Compare commits

...

31 Commits

Author SHA1 Message Date
Michael Ficocelli a4b0f22a2e DNET: Update changelog (#2679) 2026-04-20 16:57:23 -07:00
catloversg 2aa5092d85 BLADEBURNER: Store BlackOp team count in save data (#2675) 2026-04-19 12:20:08 -07:00
catloversg a7409a01cc UI: Ensure intelligence override is a positive integer (#2673) 2026-04-19 12:09:11 -07:00
catloversg a99109d9c7 BUGFIX: Follow-up to #2660 (#2666) 2026-04-19 12:06:54 -07:00
catloversg 95af138c39 BLADEBURNER: Restrict team count of Ops/BlackOps to total team size (#2672) 2026-04-17 14:32:22 -07:00
Mathekatze f5bbc26495 MISC: Clear recent scripts when installing augmentations (#2670) 2026-04-17 14:20:07 -07:00
catloversg f8ec7f4294 CODEBASE: Fix passive event listener warning (#2671) 2026-04-17 14:09:12 -07:00
catloversg c06c6590c9 BUGFIX: calculateExp throws errors in edge cases (#2667) 2026-04-17 00:57:50 -07:00
catloversg 45bce6e45e MISC: Reduce achievements check interval (#2650) 2026-04-15 18:47:29 -07:00
catloversg c21d1f44b2 UI: Add button to open Faction page from Gang UI (#2655) 2026-04-14 15:53:40 -07:00
catloversg 956e00f789 BUGFIX: Intelligence data is incorrectly migrated when Intelligence is not unlocked (#2660) 2026-04-14 15:20:01 -07:00
catloversg c5536d252b MISC: Update "Last updated" date in changelog (#2658) 2026-04-13 13:10:59 +07:00
catloversg a99ca64455 MISC: Update changelog (#2657) 2026-04-13 11:32:39 +07:00
catloversg cb14655325 MISC: Update description of "cat" command (#2654) 2026-04-12 17:06:27 -07:00
catloversg 9ab3e0bcb4 DOCUMENTATION: Update tutorial script for buying cloud servers (#2653) 2026-04-12 17:05:56 -07:00
catloversg cc9144c01b UI: Use exponential notation when formatting very small HP or thread values (#2656) 2026-04-12 16:49:30 -07:00
Lee Stutzman fb3fa00b3d API: Add weakenEffect to formulas.hacking namespace (#2626) 2026-04-10 16:36:45 -07:00
Lee Stutzman 8cbd6ff9e1 BUGFIX: Fix tab completion for multi-word quoted autocomplete options (#2612) 2026-04-10 16:36:11 -07:00
Michael Ficocelli 00a1bc2f6e DNET: Remove bonus time effect on authentication and heartbleed speed; fix ram rounding (#2627) 2026-04-10 16:04:05 -07:00
Lee Stutzman be6fcd206f API: Rename ns.gang.getOtherGangInformation to getAllGangInformation (#2635) 2026-04-10 15:59:39 -07:00
catloversg a6a112198e WORKFLOW: Allow specifying commit hash id when building artifacts (#2652) 2026-04-10 15:56:51 -07:00
catloversg 732aadb2d6 UI: Add hooks to sidebar for players to attach custom content (#2651) 2026-04-10 15:54:54 -07:00
catloversg 85c9ac0181 TOOL: Remove redundant "$" from JS/TS regex in webpack config (#2649) 2026-04-10 15:50:52 -07:00
catloversg e232f37550 BLADEBURNER: Add tooltips explaining why skill upgrades are disabled (#2648) 2026-04-10 15:50:07 -07:00
catloversg 6074721c59 MISC: Update description of "BN9: Challenge" achievement (#2647) 2026-04-10 15:46:47 -07:00
catloversg 09e46d757b CLI: Add "hidden" mkdir command (#2646) 2026-04-10 15:45:51 -07:00
catloversg 5cb0d559df UI: Consistently calculate BitNode "level" (#2645) 2026-04-10 15:45:18 -07:00
catloversg 54287e5f7f DOCUMENTATION: Update RAM cost of hacknet APIs and remove unnecessary RAM cost docs (#2639) 2026-04-09 17:27:17 -07:00
catloversg d25b1676ab MISC: Apply SF override to charisma calculations (#2642) 2026-04-09 17:25:51 -07:00
Lee Stutzman d6299becd6 DOCUMENTATION: Clarify scp and exec darknet permissions in API docs (#2634) 2026-04-09 17:24:37 -07:00
catloversg 19b137e2fb UI: Remove unnecessary max-width of tab list in in-game editor (#2643) 2026-04-09 17:22:53 -07:00
113 changed files with 1835 additions and 1106 deletions
+16
View File
@@ -2,7 +2,17 @@ name: Build artifacts
on: on:
workflow_dispatch: workflow_dispatch:
inputs:
git-sha:
description: "Commit SHA-1 to checkout"
required: false
default: ""
workflow_call: workflow_call:
inputs:
git-sha:
type: string
required: false
default: ""
env: env:
GH_TOKEN: ${{ github.token }} GH_TOKEN: ${{ github.token }}
@@ -13,6 +23,8 @@ jobs:
runs-on: windows-latest runs-on: windows-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
with:
ref: ${{ github.event.inputs.git-sha || inputs.git-sha || github.sha }}
- name: Use Node.js 24 - name: Use Node.js 24
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:
@@ -46,6 +58,8 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
with:
ref: ${{ github.event.inputs.git-sha || inputs.git-sha || github.sha }}
- name: Use Node.js 24 - name: Use Node.js 24
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:
@@ -77,6 +91,8 @@ jobs:
runs-on: macos-latest runs-on: macos-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
with:
ref: ${{ github.event.inputs.git-sha || inputs.git-sha || github.sha }}
- name: Use Node.js 24 - name: Use Node.js 24
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:
+1 -1
View File
@@ -12,7 +12,7 @@ Default value:
- All boolean options: false - All boolean options: false
If you specify intelligenceOverride, it must be a non-negative integer. If you specify intelligenceOverride, it must be a positive integer.
**Signature:** **Signature:**
@@ -1,15 +1,15 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. --> <!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [bitburner](./bitburner.md) &gt; [Gang](./bitburner.gang.md) &gt; [getOtherGangInformation](./bitburner.gang.getotherganginformation.md) [Home](./index.md) &gt; [bitburner](./bitburner.md) &gt; [Gang](./bitburner.gang.md) &gt; [getAllGangInformation](./bitburner.gang.getallganginformation.md)
## Gang.getOtherGangInformation() method ## Gang.getAllGangInformation() method
Get information about all gangs. Get information about all gangs.
**Signature:** **Signature:**
```typescript ```typescript
getOtherGangInformation(): Record<string, GangOtherInfoObject>; getAllGangInformation(): Record<string, GangOtherInfoObject>;
``` ```
**Returns:** **Returns:**
+11 -11
View File
@@ -61,6 +61,17 @@ Check if you can recruit a new gang member.
Create a gang. Create a gang.
</td></tr>
<tr><td>
[getAllGangInformation()](./bitburner.gang.getallganginformation.md)
</td><td>
Get information about all gangs.
</td></tr> </td></tr>
<tr><td> <tr><td>
@@ -182,17 +193,6 @@ Get information about a specific gang member.
List all gang members. List all gang members.
</td></tr>
<tr><td>
[getOtherGangInformation()](./bitburner.gang.getotherganginformation.md)
</td><td>
Get information about all gangs.
</td></tr> </td></tr>
<tr><td> <tr><td>
+11
View File
@@ -126,6 +126,17 @@ Calculate hack percent for one thread. (Ex: 0.25 would steal 25% of the server's
Calculate hack time. Calculate hack time.
</td></tr>
<tr><td>
[weakenEffect(threads, cores)](./bitburner.hackingformulas.weakeneffect.md)
</td><td>
Calculate the security decrease from a weaken operation. Unlike other hacking formulas, weaken effect depends only on thread count and core count, not on server or player properties. The core bonus formula is .
</td></tr> </td></tr>
<tr><td> <tr><td>
@@ -0,0 +1,72 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [bitburner](./bitburner.md) &gt; [HackingFormulas](./bitburner.hackingformulas.md) &gt; [weakenEffect](./bitburner.hackingformulas.weakeneffect.md)
## HackingFormulas.weakenEffect() method
Calculate the security decrease from a weaken operation. Unlike other hacking formulas, weaken effect depends only on thread count and core count, not on server or player properties. The core bonus formula is .
**Signature:**
```typescript
weakenEffect(threads: number, cores?: number): number;
```
## Parameters
<table><thead><tr><th>
Parameter
</th><th>
Type
</th><th>
Description
</th></tr></thead>
<tbody><tr><td>
threads
</td><td>
number
</td><td>
Number of threads running weaken.
</td></tr>
<tr><td>
cores
</td><td>
number
</td><td>
_(Optional)_ Number of cores on the host server. Default 1.
</td></tr>
</tbody></table>
**Returns:**
number
The security decrease amount.
@@ -72,7 +72,7 @@ Cost of upgrading the specified Hacknet Node's cache.
## Remarks ## Remarks
RAM cost: 0 GB RAM cost: 0.5 GB
This function is only applicable for Hacknet Servers (the upgraded version of a Hacknet Node). This function is only applicable for Hacknet Servers (the upgraded version of a Hacknet Node).
@@ -72,7 +72,7 @@ Cost of upgrading the specified Hacknet Node's number of cores.
## Remarks ## Remarks
RAM cost: 0 GB RAM cost: 0.5 GB
Returns the cost of upgrading the number of cores of the specified Hacknet Node by n. Returns the cost of upgrading the number of cores of the specified Hacknet Node by n.
@@ -54,7 +54,7 @@ Level of the upgrade.
## Remarks ## Remarks
RAM cost: 0 GB RAM cost: 0.5 GB
This function is only applicable for Hacknet Servers (the upgraded version of a Hacknet Node). This function is only applicable for Hacknet Servers (the upgraded version of a Hacknet Node).
@@ -19,7 +19,7 @@ An array containing the available upgrades
## Remarks ## Remarks
RAM cost: 0 GB RAM cost: 0.5 GB
This function is only applicable for Hacknet Servers (the upgraded version of a Hacknet Node). This function is only applicable for Hacknet Servers (the upgraded version of a Hacknet Node).
@@ -72,7 +72,7 @@ Cost of upgrading the specified Hacknet Node.
## Remarks ## Remarks
RAM cost: 0 GB RAM cost: 0.5 GB
Returns the cost of upgrading the specified Hacknet Node by n levels. Returns the cost of upgrading the specified Hacknet Node by n levels.
+1 -1
View File
@@ -56,7 +56,7 @@ Object containing a variety of stats about the specified Hacknet Node.
## Remarks ## Remarks
RAM cost: 0 GB RAM cost: 0.5 GB
Returns an object containing a variety of stats about the specified Hacknet Node. Returns an object containing a variety of stats about the specified Hacknet Node.
@@ -19,7 +19,7 @@ Cost of purchasing a new Hacknet Node.
## Remarks ## Remarks
RAM cost: 0 GB RAM cost: 0.5 GB
Returns the cost of purchasing a new Hacknet Node. Returns the cost of purchasing a new Hacknet Node.
@@ -72,7 +72,7 @@ Cost of upgrading the specified Hacknet Node's RAM.
## Remarks ## Remarks
RAM cost: 0 GB RAM cost: 0.5 GB
Returns the cost of upgrading the RAM of the specified Hacknet Node n times. Returns the cost of upgrading the RAM of the specified Hacknet Node n times.
+1 -1
View File
@@ -19,7 +19,7 @@ Multiplier.
## Remarks ## Remarks
RAM cost: 0 GB RAM cost: 0.5 GB
This function is only applicable for Hacknet Servers (the upgraded version of a Hacknet Node). This function is only applicable for Hacknet Servers (the upgraded version of a Hacknet Node).
@@ -19,7 +19,7 @@ Multiplier.
## Remarks ## Remarks
RAM cost: 0 GB RAM cost: 0.5 GB
This function is only applicable for Hacknet Servers (the upgraded version of a Hacknet Node). This function is only applicable for Hacknet Servers (the upgraded version of a Hacknet Node).
+1 -1
View File
@@ -19,7 +19,7 @@ Number of hashes you can store.
## Remarks ## Remarks
RAM cost: 0 GB RAM cost: 0.5 GB
This function is only applicable for Hacknet Servers (the upgraded version of a Hacknet Node). This function is only applicable for Hacknet Servers (the upgraded version of a Hacknet Node).
+1 -1
View File
@@ -72,7 +72,7 @@ Number of hashes required for the specified upgrade.
## Remarks ## Remarks
RAM cost: 0 GB RAM cost: 0.5 GB
This function is only applicable for Hacknet Servers (the upgraded version of a Hacknet Node). This function is only applicable for Hacknet Servers (the upgraded version of a Hacknet Node).
+1 -1
View File
@@ -19,5 +19,5 @@ Maximum number of hacknet nodes.
## Remarks ## Remarks
RAM cost: 0 GB RAM cost: 0.5 GB
+1 -1
View File
@@ -19,7 +19,7 @@ Number of hashes you have.
## Remarks ## Remarks
RAM cost: 0 GB RAM cost: 0.5 GB
This function is only applicable for Hacknet Servers (the upgraded version of a Hacknet Node). This function is only applicable for Hacknet Servers (the upgraded version of a Hacknet Node).
+1 -1
View File
@@ -19,7 +19,7 @@ Number of hacknet nodes.
## Remarks ## Remarks
RAM cost: 0 GB RAM cost: 0.5 GB
Returns the number of Hacknet Nodes you own. Returns the number of Hacknet Nodes you own.
+1 -1
View File
@@ -19,7 +19,7 @@ The index of the Hacknet Node or if the player cannot afford to purchase a new H
## Remarks ## Remarks
RAM cost: 0 GB RAM cost: 0.5 GB
Purchases a new Hacknet Node. Returns a number with the index of the Hacknet Node. This index is equivalent to the number at the end of the Hacknet Nodes name (e.g. The Hacknet Node named `hacknet-node-4` will have an index of 4). Purchases a new Hacknet Node. Returns a number with the index of the Hacknet Node. This index is equivalent to the number at the end of the Hacknet Nodes name (e.g. The Hacknet Node named `hacknet-node-4` will have an index of 4).
+1 -1
View File
@@ -88,7 +88,7 @@ True if the upgrade is successfully purchased, and false otherwise.
## Remarks ## Remarks
RAM cost: 0 GB RAM cost: 0.5 GB
This function is only applicable for Hacknet Servers (the upgraded version of a Hacknet Node). This function is only applicable for Hacknet Servers (the upgraded version of a Hacknet Node).
+1 -1
View File
@@ -72,7 +72,7 @@ True if the Hacknet Nodes cache level is successfully upgraded, false otherwi
## Remarks ## Remarks
RAM cost: 0 GB RAM cost: 0.5 GB
This function is only applicable for Hacknet Servers (the upgraded version of a Hacknet Node). This function is only applicable for Hacknet Servers (the upgraded version of a Hacknet Node).
+1 -1
View File
@@ -72,7 +72,7 @@ True if the Hacknet Nodes cores are successfully purchased, false otherwise.
## Remarks ## Remarks
RAM cost: 0 GB RAM cost: 0.5 GB
Tries to purchase n cores for the specified Hacknet Node. Tries to purchase n cores for the specified Hacknet Node.
+1 -1
View File
@@ -72,7 +72,7 @@ True if the Hacknet Nodes level is successfully upgraded, false otherwise.
## Remarks ## Remarks
RAM cost: 0 GB RAM cost: 0.5 GB
Tries to upgrade the level of the specified Hacknet Node by n. Tries to upgrade the level of the specified Hacknet Node by n.
+1 -1
View File
@@ -72,7 +72,7 @@ True if the Hacknet Nodes RAM is successfully upgraded, false otherwise.
## Remarks ## Remarks
RAM cost: 0 GB RAM cost: 0.5 GB
Tries to upgrade the specified Hacknet Nodes RAM n times. Note that each upgrade doubles the Nodes RAM. So this is equivalent to multiplying the Nodes RAM by 2 n. Tries to upgrade the specified Hacknet Nodes RAM n times. Note that each upgrade doubles the Nodes RAM. So this is equivalent to multiplying the Nodes RAM by 2 n.
+1 -1
View File
@@ -123,7 +123,7 @@ Default value:
- All boolean options: false - All boolean options: false
If you specify intelligenceOverride, it must be a non-negative integer. If you specify intelligenceOverride, it must be a positive integer.
</td></tr> </td></tr>
+2 -6
View File
@@ -6,18 +6,14 @@
Arguments passed into the script. Arguments passed into the script.
These arguments can be accessed as a normal array by using the `[]` operator (`args[0]`<!-- -->, `args[1]`<!-- -->, etc...). Arguments can be string, number, or boolean. Use `args.length` to get the number of arguments that were passed into a script.
**Signature:** **Signature:**
```typescript ```typescript
readonly args: ScriptArg[]; readonly args: ScriptArg[];
``` ```
## Remarks
RAM cost: 0 GB
Arguments passed into a script can be accessed as a normal array by using the `[]` operator (`args[0]`<!-- -->, `args[1]`<!-- -->, etc...). Arguments can be string, number, or boolean. Use `args.length` to get the number of arguments that were passed into a script.
## Example ## Example
`run example.js 7 text true` `run example.js 7 text true`
-5
View File
@@ -11,8 +11,3 @@ Namespace for [Bladeburner](./bitburner.bladeburner.md) functions. Contains spoi
```typescript ```typescript
readonly bladeburner: Bladeburner; readonly bladeburner: Bladeburner;
``` ```
## Remarks
RAM cost: 0 GB
-5
View File
@@ -11,8 +11,3 @@ Namespace for [cloud](./bitburner.cloud.md) functions.
```typescript ```typescript
readonly cloud: Cloud; readonly cloud: Cloud;
``` ```
## Remarks
RAM cost: 0 GB
-5
View File
@@ -11,8 +11,3 @@ Namespace for [coding contract](./bitburner.codingcontract.md) functions.
```typescript ```typescript
readonly codingcontract: CodingContract; readonly codingcontract: CodingContract;
``` ```
## Remarks
RAM cost: 0 GB
-5
View File
@@ -11,8 +11,3 @@ Namespace for [corporation](./bitburner.corporation.md) functions. Contains spoi
```typescript ```typescript
readonly corporation: Corporation; readonly corporation: Corporation;
``` ```
## Remarks
RAM cost: 0 GB
-5
View File
@@ -11,8 +11,3 @@ Namespace for darknet functions. Contains spoilers.
```typescript ```typescript
readonly dnet: Darknet; readonly dnet: Darknet;
``` ```
## Remarks
RAM cost: 0 GB
+1
View File
@@ -132,4 +132,5 @@ ns.exec("generic-hack.js", "joesguns", {threads: 10});
// arguments to the script. // arguments to the script.
ns.exec("foo.js", "foodnstuff", 5, 1, "test"); ns.exec("foo.js", "foodnstuff", 5, 1, "test");
``` ```
For darknet servers: A session must be established with the target server, and the script must be running on a server that is directly connected to the target, or the target must have a backdoor or stasis link installed.
-5
View File
@@ -11,8 +11,3 @@ Namespace for [formatting](./bitburner.format.md) functions.
```typescript ```typescript
readonly format: Format; readonly format: Format;
``` ```
## Remarks
RAM cost: 0 GB
-5
View File
@@ -11,8 +11,3 @@ Namespace for [formulas](./bitburner.formulas.md) functions.
```typescript ```typescript
readonly formulas: Formulas; readonly formulas: Formulas;
``` ```
## Remarks
RAM cost: 0 GB
-5
View File
@@ -11,8 +11,3 @@ Namespace for [gang](./bitburner.gang.md) functions. Contains spoilers.
```typescript ```typescript
readonly gang: Gang; readonly gang: Gang;
``` ```
## Remarks
RAM cost: 0 GB
-5
View File
@@ -11,8 +11,3 @@ Namespace for [Go](./bitburner.go.md) functions.
```typescript ```typescript
readonly go: Go; readonly go: Go;
``` ```
## Remarks
RAM cost: 0 GB
-5
View File
@@ -11,8 +11,3 @@ Namespace for [grafting](./bitburner.grafting.md) functions. Contains spoilers.
```typescript ```typescript
readonly grafting: Grafting; readonly grafting: Grafting;
``` ```
## Remarks
RAM cost: 0 GB
-5
View File
@@ -11,8 +11,3 @@ Namespace for [hacknet](./bitburner.hacknet.md) functions. Some of this API cont
```typescript ```typescript
readonly hacknet: Hacknet; readonly hacknet: Hacknet;
``` ```
## Remarks
RAM cost: 4 GB.
-5
View File
@@ -11,8 +11,3 @@ Namespace for [infiltration](./bitburner.infiltration.md) functions.
```typescript ```typescript
readonly infiltration: Infiltration; readonly infiltration: Infiltration;
``` ```
## Remarks
RAM cost: 0 GB
+2
View File
@@ -69,6 +69,8 @@ Description
Arguments passed into the script. Arguments passed into the script.
These arguments can be accessed as a normal array by using the `[]` operator (`args[0]`<!-- -->, `args[1]`<!-- -->, etc...). Arguments can be string, number, or boolean. Use `args.length` to get the number of arguments that were passed into a script.
</td></tr> </td></tr>
<tr><td> <tr><td>
+1 -1
View File
@@ -112,5 +112,5 @@ const server = ns.args[0];
const files = ["hack.js", "weaken.js", "grow.js"]; const files = ["hack.js", "weaken.js", "grow.js"];
ns.scp(files, server, "home"); ns.scp(files, server, "home");
``` ```
For password-protected servers (such as darknet servers), a session must be established with the destination server before using this function. (The source server does not require a session.) For darknet servers: The destination requires a session, but unlike [exec](./bitburner.ns.exec.md)<!-- -->, does not require a direct connection — scp works at any distance. The source server has no darknet requirements (no session or connection needed). Use [dnet.authenticate](./bitburner.darknet.authenticate.md) (requires direct connection) or [dnet.connectToSession](./bitburner.darknet.connecttosession.md) (at any distance) to establish a session.
-5
View File
@@ -11,8 +11,3 @@ Namespace for [singularity](./bitburner.singularity.md) functions. Contains spoi
```typescript ```typescript
readonly singularity: Singularity; readonly singularity: Singularity;
``` ```
## Remarks
RAM cost: 0 GB
-5
View File
@@ -11,8 +11,3 @@ Namespace for [sleeve](./bitburner.sleeve.md) functions. Contains spoilers.
```typescript ```typescript
readonly sleeve: Sleeve; readonly sleeve: Sleeve;
``` ```
## Remarks
RAM cost: 0 GB
-5
View File
@@ -11,8 +11,3 @@ Namespace for [Stanek](./bitburner.stanek.md) functions. Contains spoilers.
```typescript ```typescript
readonly stanek: Stanek; readonly stanek: Stanek;
``` ```
## Remarks
RAM cost: 0 GB
-5
View File
@@ -11,8 +11,3 @@ Namespace for [stock](./bitburner.stock.md) functions.
```typescript ```typescript
readonly stock: Stock; readonly stock: Stock;
``` ```
## Remarks
RAM cost: 0 GB
-5
View File
@@ -11,8 +11,3 @@ Namespace for [user interface](./bitburner.userinterface.md) functions.
```typescript ```typescript
readonly ui: UserInterface; readonly ui: UserInterface;
``` ```
## Remarks
RAM cost: 0 GB
+1 -1
View File
@@ -454,7 +454,7 @@
"CHALLENGE_BN9": { "CHALLENGE_BN9": {
"ID": "CHALLENGE_BN9", "ID": "CHALLENGE_BN9",
"Name": "BN9: Challenge", "Name": "BN9: Challenge",
"Description": "Destroy BN9 without using hacknet servers." "Description": "Destroy BN9 without using hacknet servers or hacknet nodes."
}, },
"CHALLENGE_BN10": { "CHALLENGE_BN10": {
"ID": "CHALLENGE_BN10", "ID": "CHALLENGE_BN10",
+1 -10
View File
@@ -4,19 +4,10 @@ import { AchievementList } from "./AchievementList";
import { achievements } from "./Achievements"; import { achievements } from "./Achievements";
import { Box, Typography } from "@mui/material"; import { Box, Typography } from "@mui/material";
import { Player } from "@player"; import { Player } from "@player";
import { makeStyles } from "tss-react/mui";
const useStyles = makeStyles()({
root: {
width: 50,
userSelect: "none",
},
});
export function AchievementsRoot(): JSX.Element { export function AchievementsRoot(): JSX.Element {
const { classes } = useStyles();
return ( return (
<div className={classes.root} style={{ width: "100%" }}> <div style={{ width: "100%" }}>
<Typography variant="h4">Achievements</Typography> <Typography variant="h4">Achievements</Typography>
<Box mx={2}> <Box mx={2}>
<Typography> <Typography>
+9
View File
@@ -93,3 +93,12 @@ export function finishBitNode() {
} }
wd.backdoorInstalled = true; wd.backdoorInstalled = true;
} }
/**
* BitNode level is not something that is stored, but rather calculated from the current BN and SF level. The concept
* appeared because saying "Enter BN1.2" is shorter than saying "Enter BN1 with SF1.1". This is how we display it in the
* BitVerse UI and other places. This function is used to consistently calculate this "level".
*/
export function getBitNodeLevel(bn = Player.bitNodeN, sfLevel = Player.activeSourceFileLvl(bn)): number {
return Math.min(sfLevel + 1, bn === 12 ? Number.MAX_VALUE : 3);
}
+3 -3
View File
@@ -271,13 +271,13 @@ function IntelligenceOverride({
disabled={!enabled} disabled={!enabled}
value={intelligenceOverride !== undefined ? intelligenceOverride : ""} value={intelligenceOverride !== undefined ? intelligenceOverride : ""}
onChange={(event) => { onChange={(event) => {
// Empty string will be automatically changed to "0". // Empty string will be automatically changed to "1".
if (event.target.value === "") { if (event.target.value === "") {
callbacks.setIntelligenceOverride(0); callbacks.setIntelligenceOverride(1);
return; return;
} }
const value = Number.parseInt(event.target.value); const value = Number.parseInt(event.target.value);
if (!Number.isInteger(value) || value < 0) { if (!Number.isInteger(value) || value < 1) {
return; return;
} }
callbacks.setIntelligenceOverride(value); callbacks.setIntelligenceOverride(value);
@@ -20,7 +20,7 @@ import { StatsRow } from "../../ui/React/StatsRow";
import { defaultMultipliers, getBitNodeMultipliers } from "../BitNode"; import { defaultMultipliers, getBitNodeMultipliers } from "../BitNode";
import { BitNodeMultipliers } from "../BitNodeMultipliers"; import { BitNodeMultipliers } from "../BitNodeMultipliers";
import { PartialRecord, getRecordEntries } from "../../Types/Record"; import { PartialRecord, getRecordEntries } from "../../Types/Record";
import { canAccessBitNodeFeature } from "../BitNodeUtils"; import { canAccessBitNodeFeature, getBitNodeLevel } from "../BitNodeUtils";
interface IProps { interface IProps {
n: number; n: number;
@@ -56,8 +56,7 @@ export const BitNodeMultipliersDisplay = ({ n, level, hideMultsIfCannotAccessFea
// If not, then we have to assume that we want the next level up from the // If not, then we have to assume that we want the next level up from the
// current node's source file, so we get the min of that, the SF's max level, // current node's source file, so we get the min of that, the SF's max level,
// or if it's BN12, ∞ // or if it's BN12, ∞
const maxSfLevel = n === 12 ? Number.MAX_VALUE : 3; const mults = getBitNodeMultipliers(n, level ?? getBitNodeLevel(n));
const mults = getBitNodeMultipliers(n, level ?? Math.min(Player.activeSourceFileLvl(n) + 1, maxSfLevel));
return ( return (
<Box sx={{ columnCount: 2, columnGap: 1, mb: n === 1 ? 0 : -2 }}> <Box sx={{ columnCount: 2, columnGap: 1, mb: n === 1 ? 0 : -2 }}>
+2 -1
View File
@@ -10,6 +10,7 @@ import Button from "@mui/material/Button";
import { BitNodeMultiplierDescription } from "./BitnodeMultipliersDescription"; import { BitNodeMultiplierDescription } from "./BitnodeMultipliersDescription";
import { BitNodeAdvancedOptions } from "./BitNodeAdvancedOptions"; import { BitNodeAdvancedOptions } from "./BitNodeAdvancedOptions";
import { JSONMap } from "../../Types/Jsonable"; import { JSONMap } from "../../Types/Jsonable";
import { getBitNodeLevel } from "../BitNodeUtils";
interface IProps { interface IProps {
open: boolean; open: boolean;
@@ -37,7 +38,7 @@ export function PortalModal(props: IProps): React.ReactElement {
const bitNode = BitNodes[bitNodeKey]; const bitNode = BitNodes[bitNodeKey];
if (bitNode == null) throw new Error(`Could not find BitNode object for number: ${props.n}`); if (bitNode == null) throw new Error(`Could not find BitNode object for number: ${props.n}`);
const maxSourceFileLevel = props.n === 12 ? "∞" : "3"; const maxSourceFileLevel = props.n === 12 ? "∞" : "3";
const newLevel = Math.min(props.level + 1, props.n === 12 ? Number.MAX_VALUE : 3); const newLevel = getBitNodeLevel(props.n, props.level);
let currentSourceFiles = new Map(Player.sourceFiles); let currentSourceFiles = new Map(Player.sourceFiles);
if (!props.flume) { if (!props.flume) {
+25 -4
View File
@@ -6,6 +6,8 @@ import { ActionClass, ActionParams } from "./Action";
import { operationSkillSuccessBonus, operationTeamSuccessBonus } from "./Operation"; import { operationSkillSuccessBonus, operationTeamSuccessBonus } from "./Operation";
import { getEnumHelper } from "../../utils/EnumHelper"; import { getEnumHelper } from "../../utils/EnumHelper";
import type { TeamActionWithCasualties } from "./TeamCasualties"; import type { TeamActionWithCasualties } from "./TeamCasualties";
import { constructorsForReviver, Generic_fromJSON, type IReviverValue } from "../../utils/JSONReviver";
import { clampInteger } from "../../utils/helpers/clampNumber";
interface BlackOpParams { interface BlackOpParams {
name: BladeburnerBlackOpName; name: BladeburnerBlackOpName;
@@ -32,11 +34,11 @@ export class BlackOperation extends ActionClass implements TeamActionWithCasualt
return getEnumHelper("BladeburnerBlackOpName").isMember(name); return getEnumHelper("BladeburnerBlackOpName").isMember(name);
} }
constructor(params: ActionParams & BlackOpParams) { constructor(params: (ActionParams & BlackOpParams) | null = null) {
super(params); super(params);
this.name = params.name; this.name = params?.name ?? BladeburnerBlackOpName.OperationTyphoon;
this.reqdRank = params.reqdRank; this.reqdRank = params?.reqdRank ?? 0;
this.n = params.n; this.n = params?.n ?? 0;
} }
getAvailability(bladeburner: Bladeburner): Availability { getAvailability(bladeburner: Bladeburner): Availability {
@@ -65,4 +67,23 @@ export class BlackOperation extends ActionClass implements TeamActionWithCasualt
getTeamSuccessBonus = operationTeamSuccessBonus; getTeamSuccessBonus = operationTeamSuccessBonus;
getActionTypeSkillSuccessBonus = operationSkillSuccessBonus; getActionTypeSkillSuccessBonus = operationSkillSuccessBonus;
toJSON(): IReviverValue {
return {
ctor: "BlackOperation",
data: {
teamCount: this.teamCount,
},
};
}
loadData(loadedObject: BlackOperation): void {
this.teamCount = clampInteger(loadedObject.teamCount, 0);
}
static fromJSON(value: IReviverValue): BlackOperation {
return Generic_fromJSON(BlackOperation, value.data);
}
} }
constructorsForReviver.BlackOperation = BlackOperation;
+10 -4
View File
@@ -44,12 +44,18 @@ export function resolveTeamCasualties(action: TeamActionWithCasualties, team: Op
*/ */
const losses = const losses =
minCasualties <= maxCasualties ? team.getTeamCasualtiesRoll(minCasualties, maxCasualties) : minCasualties; minCasualties <= maxCasualties ? team.getTeamCasualtiesRoll(minCasualties, maxCasualties) : minCasualties;
team.teamSize -= losses; // Calculate the new teamSize in a temporary variable and call the setter team.teamSize ONCE.
if (team.teamSize < team.sleeveSize) { // Note that it's important to call the setter only once; otherwise, the team count of each operation won't be reset
team.killRandomSupportingSleeves(team.sleeveSize - team.teamSize); // correctly.
// For example, if _teamSize is 9 (1 team member + 8 support sleeves) and "losses" is 9, calling the setter with
// (team.teamSize - losses) will set teamCount of ops/blackOps to 0 while it should be 8.
let newTeamSize = team.teamSize - losses;
if (newTeamSize < team.sleeveSize) {
team.killRandomSupportingSleeves(team.sleeveSize - newTeamSize);
// If this happens, all team members died and some sleeves took damage. In this case, teamSize = sleeveSize. // If this happens, all team members died and some sleeves took damage. In this case, teamSize = sleeveSize.
team.teamSize = team.sleeveSize; newTeamSize = team.sleeveSize;
} }
team.teamSize = newTeamSize;
team.teamLost += losses; team.teamLost += losses;
return losses; return losses;
+50 -9
View File
@@ -47,7 +47,7 @@ import { createContracts, loadContractsData } from "./data/Contracts";
import { createOperations, loadOperationsData } from "./data/Operations"; import { createOperations, loadOperationsData } from "./data/Operations";
import { clampInteger, clampNumber } from "../utils/helpers/clampNumber"; import { clampInteger, clampNumber } from "../utils/helpers/clampNumber";
import { parseCommand } from "../Terminal/Parser"; import { parseCommand } from "../Terminal/Parser";
import { BlackOperations } from "./data/BlackOperations"; import { createBlackOperations, loadBlackOperationsData } from "./data/BlackOperations";
import { GeneralActions } from "./data/GeneralActions"; import { GeneralActions } from "./data/GeneralActions";
import { PlayerObject } from "../PersonObjects/Player/PlayerObject"; import { PlayerObject } from "../PersonObjects/Player/PlayerObject";
import { Sleeve } from "../PersonObjects/Sleeve/Sleeve"; import { Sleeve } from "../PersonObjects/Sleeve/Sleeve";
@@ -72,7 +72,31 @@ export class Bladeburner implements OperationTeam {
skillPoints = 0; skillPoints = 0;
totalSkillPoints = 0; totalSkillPoints = 0;
teamSize = 0; /**
* Do NOT directly read and write this field. You must use the getter/setter.
* We use _teamSize instead of a private field #teamSize to reduce the complexity of saving/loading code.
*/
_teamSize = 0;
get teamSize() {
return this._teamSize;
}
set teamSize(value: number) {
// Ensure teamSize is a non-negative integer.
let newSize = value;
if (!Number.isInteger(newSize) || newSize < 0) {
newSize = 0;
}
// Early return if there is no change.
if (this._teamSize === newSize) {
return;
}
this._teamSize = newSize;
// Reduce teamCount of actions if it's greater than the team size.
for (const action of [...Object.values(this.operations), ...Object.values(this.blackOperations)]) {
action.teamCount = Math.min(action.teamCount, this._teamSize);
}
}
get sleeveSize() { get sleeveSize() {
return Player.sleevesSupportingBladeburner().length; return Player.sleevesSupportingBladeburner().length;
} }
@@ -96,9 +120,13 @@ export class Bladeburner implements OperationTeam {
staminaBonus = 0; staminaBonus = 0;
maxStamina = 1; maxStamina = 1;
stamina = 1; stamina = 1;
// Contracts and operations are stored on the Bladeburner object even though they are global so that they can utilize save/load of the main bladeburner object // Contracts, operations and blackOps are stored on the Bladeburner object even though they are global so that they
// can utilize save/load of the main bladeburner object
contracts: Record<BladeburnerContractName, Contract>; contracts: Record<BladeburnerContractName, Contract>;
operations: Record<BladeburnerOperationName, Operation>; operations: Record<BladeburnerOperationName, Operation>;
blackOperations: Record<BladeburnerBlackOpName, BlackOperation>;
// Array for quick lookup by BlackOp number
blackOperationArray: BlackOperation[];
numBlackOpsComplete = 0; numBlackOpsComplete = 0;
logging = { logging = {
general: true, general: true,
@@ -119,6 +147,11 @@ export class Bladeburner implements OperationTeam {
constructor() { constructor() {
this.contracts = createContracts(); this.contracts = createContracts();
this.operations = createOperations(); this.operations = createOperations();
this.blackOperations = createBlackOperations();
this.blackOperationArray = Object.values(this.blackOperations).sort((a, b) => (a.n < b.n ? -1 : 1));
if (!this.blackOperationArray.every((blackOp, i) => blackOp.n === i)) {
throw new Error("blackOperationArray is not initialized with correct indices");
}
} }
// Initialization code that is dependent on Player is here instead of in the constructor // Initialization code that is dependent on Player is here instead of in the constructor
@@ -1407,7 +1440,7 @@ export class Bladeburner implements OperationTeam {
case BladeburnerActionType.Operation: case BladeburnerActionType.Operation:
return this.operations[actionId.name]; return this.operations[actionId.name];
case BladeburnerActionType.BlackOp: case BladeburnerActionType.BlackOp:
return BlackOperations[actionId.name]; return this.blackOperations[actionId.name];
case BladeburnerActionType.General: case BladeburnerActionType.General:
return GeneralActions[actionId.name]; return GeneralActions[actionId.name];
} }
@@ -1426,7 +1459,7 @@ export class Bladeburner implements OperationTeam {
case BladeburnerActionType.Operation: case BladeburnerActionType.Operation:
return this.operations[name as BladeburnerOperationName]; return this.operations[name as BladeburnerOperationName];
case BladeburnerActionType.BlackOp: case BladeburnerActionType.BlackOp:
return BlackOperations[name as BladeburnerBlackOpName]; return this.blackOperations[name as BladeburnerBlackOpName];
} }
} }
@@ -1437,9 +1470,11 @@ export class Bladeburner implements OperationTeam {
return id ? this.getActionObject(id) : null; return id ? this.getActionObject(id) : null;
} }
static keysToSave = getKeyList(Bladeburner, { removedKeys: ["skillMultipliers"] }); static keysToSave = getKeyList(Bladeburner, { removedKeys: ["skillMultipliers", "blackOperationArray"] });
// Don't load contracts or operations because of the special loading method they use, see fromJSON // Don't load contracts or operations because of the special loading method they use, see fromJSON
static keysToLoad = getKeyList(Bladeburner, { removedKeys: ["skillMultipliers", "contracts", "operations"] }); static keysToLoad = getKeyList(Bladeburner, {
removedKeys: ["skillMultipliers", "contracts", "operations", "blackOperations", "blackOperationArray"],
});
/** Serialize the current object to a JSON save state. */ /** Serialize the current object to a JSON save state. */
toJSON(): IReviverValue { toJSON(): IReviverValue {
@@ -1449,9 +1484,10 @@ export class Bladeburner implements OperationTeam {
/** Initializes a Bladeburner object from a JSON save state. */ /** Initializes a Bladeburner object from a JSON save state. */
static fromJSON(value: IReviverValue): Bladeburner { static fromJSON(value: IReviverValue): Bladeburner {
assertObject(value.data); assertObject(value.data);
// operations and contracts are not loaded directly from the save, we load them in using a different method // Contracts, operations, and black ops are not loaded directly from the save; they are loaded via a different method.
const contractsData = value.data.contracts; const contractsData = value.data.contracts;
const operationsData = value.data.operations; const operationsData = value.data.operations;
const blackOperationsData = value.data.blackOperations;
const bladeburner = Generic_fromJSON(Bladeburner, value.data, Bladeburner.keysToLoad); const bladeburner = Generic_fromJSON(Bladeburner, value.data, Bladeburner.keysToLoad);
/** /**
@@ -1472,10 +1508,11 @@ export class Bladeburner implements OperationTeam {
bladeburner.automateActionLow = loadActionIdentifier(bladeburner.automateActionLow); bladeburner.automateActionLow = loadActionIdentifier(bladeburner.automateActionLow);
} }
} }
// Loading this way allows better typesafety and also allows faithfully reconstructing contracts/operations // Loading this way allows better typesafety and also allows faithfully reconstructing contracts/operations/blackOps
// even from save data that is missing a lot of static info about the objects. // even from save data that is missing a lot of static info about the objects.
loadContractsData(contractsData, bladeburner.contracts); loadContractsData(contractsData, bladeburner.contracts);
loadOperationsData(operationsData, bladeburner.operations); loadOperationsData(operationsData, bladeburner.operations);
loadBlackOperationsData(blackOperationsData, bladeburner.blackOperations);
// Regenerate skill multiplier data, which is not included in savedata // Regenerate skill multiplier data, which is not included in savedata
bladeburner.updateSkillMultipliers(); bladeburner.updateSkillMultipliers();
// If stamina or maxStamina is invalid, we set both of them to 1 and recalculate them. // If stamina or maxStamina is invalid, we set both of them to 1 and recalculate them.
@@ -1488,6 +1525,10 @@ export class Bladeburner implements OperationTeam {
bladeburner.maxStamina = 1; bladeburner.maxStamina = 1;
bladeburner.calculateMaxStamina(); bladeburner.calculateMaxStamina();
} }
// "_teamSize" was "teamSize" in pre-v3 versions.
if ("teamSize" in value.data && Number.isFinite(value.data.teamSize)) {
bladeburner.teamSize = value.data.teamSize as number;
}
return bladeburner; return bladeburner;
} }
} }
File diff suppressed because it is too large Load Diff
+3 -1
View File
@@ -113,7 +113,9 @@ export function createContracts(): Record<BladeburnerContractName, Contract> {
export function loadContractsData(data: unknown, contracts: Record<BladeburnerContractName, Contract>) { export function loadContractsData(data: unknown, contracts: Record<BladeburnerContractName, Contract>) {
// loading data as "unknown" and typechecking it down is probably not necessary // loading data as "unknown" and typechecking it down is probably not necessary
// but this will prevent crashes even with malformed savedata // but this will prevent crashes even with malformed savedata
if (!data || typeof data !== "object") return; if (data == null || typeof data !== "object" || Array.isArray(data)) {
return;
}
assertLoadingType<Record<BladeburnerContractName, unknown>>(data); assertLoadingType<Record<BladeburnerContractName, unknown>>(data);
for (const contractName of Object.values(BladeburnerContractName)) { for (const contractName of Object.values(BladeburnerContractName)) {
const loadedContract = data[contractName]; const loadedContract = data[contractName];
+3 -1
View File
@@ -230,7 +230,9 @@ export function createOperations(): Record<BladeburnerOperationName, Operation>
export function loadOperationsData(data: unknown, operations: Record<BladeburnerOperationName, Operation>) { export function loadOperationsData(data: unknown, operations: Record<BladeburnerOperationName, Operation>) {
// loading data as "unknown" and typechecking it down is probably not necessary // loading data as "unknown" and typechecking it down is probably not necessary
// but this will prevent crashes even with malformed savedata // but this will prevent crashes even with malformed savedata
if (!data || typeof data !== "object") return; if (data == null || typeof data !== "object" || Array.isArray(data)) {
return;
}
assertLoadingType<Record<BladeburnerOperationName, unknown>>(data); assertLoadingType<Record<BladeburnerOperationName, unknown>>(data);
for (const operationName of Object.values(BladeburnerOperationName)) { for (const operationName of Object.values(BladeburnerOperationName)) {
const loadedOperation = data[operationName]; const loadedOperation = data[operationName];
+4 -4
View File
@@ -7,7 +7,7 @@ import { BlackOpElem } from "./BlackOpElem";
import { Router } from "../../ui/GameRoot"; import { Router } from "../../ui/GameRoot";
import { Page } from "../../ui/Router"; import { Page } from "../../ui/Router";
import { CorruptibleText } from "../../ui/React/CorruptibleText"; import { CorruptibleText } from "../../ui/React/CorruptibleText";
import { blackOpsArray } from "../data/BlackOperations"; import { numberOfBlackOperations } from "../data/BlackOperations";
import { finishBitNode } from "../../BitNode/BitNodeUtils"; import { finishBitNode } from "../../BitNode/BitNodeUtils";
import { Player } from "@player"; import { Player } from "@player";
@@ -16,7 +16,7 @@ interface BlackOpPageProps {
} }
export function BlackOpPage({ bladeburner }: BlackOpPageProps): React.ReactElement { export function BlackOpPage({ bladeburner }: BlackOpPageProps): React.ReactElement {
const blackOperations = blackOpsArray.slice(0, bladeburner.numBlackOpsComplete + 1).reverse(); const blackOperations = bladeburner.blackOperationArray.slice(0, bladeburner.numBlackOpsComplete + 1).reverse();
return ( return (
<> <>
@@ -36,11 +36,11 @@ export function BlackOpPage({ bladeburner }: BlackOpPageProps): React.ReactEleme
Unaffected by Charisma. Unaffected by Charisma.
</Typography> </Typography>
{bladeburner.numBlackOpsComplete >= blackOpsArray.length && ( {bladeburner.numBlackOpsComplete >= numberOfBlackOperations && (
<Button <Button
sx={{ my: 1, p: 1 }} sx={{ my: 1, p: 1 }}
onClick={() => { onClick={() => {
if (!Player.bladeburner || Player.bladeburner.numBlackOpsComplete < blackOpsArray.length) { if (!Player.bladeburner || Player.bladeburner.numBlackOpsComplete < numberOfBlackOperations) {
return; return;
} }
finishBitNode(); finishBitNode();
+11 -9
View File
@@ -3,7 +3,7 @@ import type { Bladeburner } from "../Bladeburner";
import React, { useMemo } from "react"; import React, { useMemo } from "react";
import { CopyableText } from "../../ui/React/CopyableText"; import { CopyableText } from "../../ui/React/CopyableText";
import { formatBigNumber } from "../../ui/formatNumber"; import { formatBigNumber } from "../../ui/formatNumber";
import { Box, IconButton, Paper, Typography } from "@mui/material"; import { Box, IconButton, Paper, Tooltip, Typography } from "@mui/material";
import AddIcon from "@mui/icons-material/Add"; import AddIcon from "@mui/icons-material/Add";
import CloseIcon from "@mui/icons-material/Close"; import CloseIcon from "@mui/icons-material/Close";
import { Skill } from "../Skill"; import { Skill } from "../Skill";
@@ -18,10 +18,8 @@ export function SkillElem({ skill, bladeburner, onUpgrade }: SkillElemProps): Re
const skillName = skill.name; const skillName = skill.name;
const skillLevel = bladeburner.getSkillLevel(skillName); const skillLevel = bladeburner.getSkillLevel(skillName);
const pointCost = useMemo(() => skill.calculateCost(skillLevel), [skill, skillLevel]); const pointCost = useMemo(() => skill.calculateCost(skillLevel), [skill, skillLevel]);
// No need to support "+1" button when the skill level reaches Number.MAX_SAFE_INTEGER.
const isSupported = skillLevel < Number.MAX_SAFE_INTEGER;
// Use skill.canUpgrade() instead of reimplementing all conditional checks. // Use skill.canUpgrade() instead of reimplementing all conditional checks.
const canLevel = isSupported && skill.canUpgrade(bladeburner, 1).available; const check = skill.canUpgrade(bladeburner, 1);
/** /**
* maxLvl is only useful when we check if we should show "MAX LEVEL". For the check of the icon button, we don't need * maxLvl is only useful when we check if we should show "MAX LEVEL". For the check of the icon button, we don't need
* it. This condition is checked in skill.canUpgrade(). * it. This condition is checked in skill.canUpgrade().
@@ -37,10 +35,14 @@ export function SkillElem({ skill, bladeburner, onUpgrade }: SkillElemProps): Re
<Paper sx={{ my: 1, p: 1 }}> <Paper sx={{ my: 1, p: 1 }}>
<Box display="flex" flexDirection="row" alignItems="center"> <Box display="flex" flexDirection="row" alignItems="center">
<CopyableText variant="h6" color="primary" value={skillName} /> <CopyableText variant="h6" color="primary" value={skillName} />
{!canLevel ? ( {!check.available ? (
<IconButton disabled> <Tooltip title={check.error}>
<CloseIcon /> <span>
</IconButton> <IconButton disabled>
<CloseIcon />
</IconButton>
</span>
</Tooltip>
) : ( ) : (
<IconButton onClick={onClick}> <IconButton onClick={onClick}>
<AddIcon /> <AddIcon />
@@ -51,7 +53,7 @@ export function SkillElem({ skill, bladeburner, onUpgrade }: SkillElemProps): Re
{maxLvl ? ( {maxLvl ? (
<Typography>MAX LEVEL</Typography> <Typography>MAX LEVEL</Typography>
) : ( ) : (
<Typography>Skill Points required: {isSupported ? formatBigNumber(pointCost) : "N/A"}</Typography> <Typography>Skill Points required: {formatBigNumber(pointCost)}</Typography>
)} )}
<Typography>{skill.desc}</Typography> <Typography>{skill.desc}</Typography>
</Paper> </Paper>
+11 -10
View File
@@ -15,25 +15,26 @@ interface TeamSizeModalProps {
} }
export function TeamSizeModal({ bladeburner, action, open, onClose }: TeamSizeModalProps): React.ReactElement { export function TeamSizeModal({ bladeburner, action, open, onClose }: TeamSizeModalProps): React.ReactElement {
const [teamSize, setTeamSize] = useState<number | undefined>(); const [teamSize, setTeamSize] = useState(0);
function confirmTeamSize(event: React.FormEvent): void { function confirmTeamSize(event: React.FormEvent): void {
// Prevent reloading page when submitting form // Prevent reloading page when submitting form
event.preventDefault(); event.preventDefault();
if (teamSize === undefined) return; if (!Number.isInteger(teamSize) || teamSize < 0) {
const num = Math.round(teamSize); dialogBoxCreate("Invalid value entered for number of Team Members (must be a non-negative integer)");
if (isNaN(num) || num < 0) { return;
dialogBoxCreate("Invalid value entered for number of Team Members (must be numeric and non-negative)");
} else {
action.teamCount = num;
} }
action.teamCount = teamSize;
onClose(); onClose();
} }
function onTeamSize(event: React.ChangeEvent<HTMLInputElement>): void { function onTeamSize(event: React.ChangeEvent<HTMLInputElement>): void {
const x = parseFloat(event.target.value); const newTeamSize = Number(event.target.value);
if (x > bladeburner.teamSize) setTeamSize(bladeburner.teamSize); if (newTeamSize > bladeburner.teamSize) {
else setTeamSize(x); setTeamSize(bladeburner.teamSize);
} else {
setTeamSize(newTeamSize);
}
} }
return ( return (
+26 -3
View File
@@ -7,7 +7,7 @@ export const CONSTANTS = {
VersionString: "3.0.0dev", VersionString: "3.0.0dev",
isDevBranch: true, isDevBranch: true,
isInTestEnvironment: globalThis.process?.env?.JEST_WORKER_ID !== undefined, isInTestEnvironment: globalThis.process?.env?.JEST_WORKER_ID !== undefined,
VersionNumber: 48, VersionNumber: 49,
/** Max level for any skill, assuming no multipliers. Determined by max numerical value in javascript for experience /** Max level for any skill, assuming no multipliers. Determined by max numerical value in javascript for experience
* and the skill level formula in Player.js. Note that all this means it that when experience hits MAX_INT, then * and the skill level formula in Player.js. Note that all this means it that when experience hits MAX_INT, then
@@ -111,7 +111,7 @@ export const CONSTANTS = {
// Also update Documentation/doc/en/changelog.md when appropriate (when doing a release) // Also update Documentation/doc/en/changelog.md when appropriate (when doing a release)
LatestUpdate: ` LatestUpdate: `
## v3.0.0 development version: last updated 18 February 2026 ## v3.0.0 development version: last updated 13 April 2026
### BREAKING CHANGES ### BREAKING CHANGES
@@ -143,13 +143,14 @@ export const CONSTANTS = {
- Cancel sleeve's current task when calling ns.sleeve.travel() (#2559) (@catloversg) - Cancel sleeve's current task when calling ns.sleeve.travel() (#2559) (@catloversg)
- Make ns.cloud.purchaseServer() and ns.cloud.deleteServer() use hostname as provided (#2560) (@catloversg) - Make ns.cloud.purchaseServer() and ns.cloud.deleteServer() use hostname as provided (#2560) (@catloversg)
- Make implicit string conversion consistent across all coding contracts (#2608) (@catloversg) - Make implicit string conversion consistent across all coding contracts (#2608) (@catloversg)
- Rename ns.gang.getOtherGangInformation to getAllGangInformation (#2635) (@lstutzman)
### MAJOR CHANGES ### MAJOR CHANGES
- Added Darknet, a new mechanic based on spreading through an unstable network and cracking passwords. Purchase DarkscapeNavigator.exe in the terminal to unlock access. (#2139) (@ficocelliguy)
- Balance change: IPvGO: Improve favor gain from wins to balance around the rep value of favor (#2131) (@ficocelliguy) - Balance change: IPvGO: Improve favor gain from wins to balance around the rep value of favor (#2131) (@ficocelliguy)
- Search and read NS API docs in editor tab and documentation tab (#2163) (@catloversg) - Search and read NS API docs in editor tab and documentation tab (#2163) (@catloversg)
- Balance change: Infiltration: Rebalance rewards, add min stat requirement, add market demand (#2210) (@ficocelliguy, @d0sboots, @catloversg) - Balance change: Infiltration: Rebalance rewards, add min stat requirement, add market demand (#2210) (@ficocelliguy, @d0sboots, @catloversg)
- Add Dark Net (#2139) (@ficocelliguy)
### UI ### UI
@@ -224,6 +225,10 @@ export const CONSTANTS = {
- Fix: Import save comparison popup shows wrong BN level (#2595) (@catloversg) - Fix: Import save comparison popup shows wrong BN level (#2595) (@catloversg)
- Fix: Cannot type in text boxes rendered by players' scripts when terminal tab is shown (#2615, #2622) (@lstutzman, @catloversg) - Fix: Cannot type in text boxes rendered by players' scripts when terminal tab is shown (#2615, #2622) (@lstutzman, @catloversg)
- Navigate to gym/university instead of city when stopping focusing on gym/class work (#2613) (@lstutzman) - Navigate to gym/university instead of city when stopping focusing on gym/class work (#2613) (@lstutzman)
- Ensure prompts shown by ns.prompt do not lose focus in the terminal tab (#2631) (@catloversg)
- Remove unnecessary max-width of tab list in in-game editor (#2643) (@catloversg)
- Add hooks to sidebar for players to attach custom content (#2651) (@catloversg)
- Use exponential notation when formatting very small HP or thread values (#2656) (@catloversg)
### MISC ### MISC
@@ -325,6 +330,12 @@ export const CONSTANTS = {
- Dnet: Remove packet capture (#2594) (@ficocelliguy) - Dnet: Remove packet capture (#2594) (@ficocelliguy)
- Generate more frequent and lower-reward coding contracts (#2603) (@ficocelliguy) - Generate more frequent and lower-reward coding contracts (#2603) (@ficocelliguy)
- Electron: Fix issues in edge cases of using --export-save (#2590) (@catloversg) - Electron: Fix issues in edge cases of using --export-save (#2590) (@catloversg)
- Fix recursive alias detection causing infinite recursion (#2610) (@lstutzman)
- Add "hidden" mkdir command (#2646) (@catloversg)
- Dnet: Remove bonus time effect on authentication and heartbleed speed; fix ram rounding (#2627) (@ficocelliguy)
- Fix tab completion for multi-word quoted autocomplete options (#2612) (@lstutzman)
- Add weakenEffect to formulas.hacking namespace (#2626) (@lstutzman)
- Update description of "cat" in "help" command (#2654) (@catloversg)
### DOCUMENTATION ### DOCUMENTATION
@@ -378,6 +389,9 @@ export const CONSTANTS = {
- Update mention of outdated getStockForecast API (#2578) (@catloversg) - Update mention of outdated getStockForecast API (#2578) (@catloversg)
- Fix newline issues in IPvGO docs and add missing RAM cost (#2602) (@catloversg) - Fix newline issues in IPvGO docs and add missing RAM cost (#2602) (@catloversg)
- Document coding contract's generation and rewards (#2624) (@catloversg) - Document coding contract's generation and rewards (#2624) (@catloversg)
- Clarify scp and exec darknet permissions in API docs (#2634) (@lstutzman)
- Update RAM cost of hacknet APIs and remove unnecessary RAM cost docs (#2639) (@catloversg)
- Update tutorial script for buying cloud servers (#2653) (@catloversg)
### SPOILER CHANGES - UI ### SPOILER CHANGES - UI
@@ -389,6 +403,8 @@ export const CONSTANTS = {
- Prevent ending BNs through reuse of Bladeburner UI event handler (#2574) (@catloversg) - Prevent ending BNs through reuse of Bladeburner UI event handler (#2574) (@catloversg)
- Always show Black Operations list (#2592) (@catloversg) - Always show Black Operations list (#2592) (@catloversg)
- Show hints of Sleeves mechanic in pre-endgame (#2605) (@catloversg) - Show hints of Sleeves mechanic in pre-endgame (#2605) (@catloversg)
- Consistently calculate BitNode "level" (#2645) (@catloversg)
- Add tooltips explaining why Bladeburner skill upgrades are disabled (#2648) (@catloversg)
### SPOILER CHANGES - MISC ### SPOILER CHANGES - MISC
@@ -416,6 +432,9 @@ export const CONSTANTS = {
- Add APIs to get rank gain and rank loss of an action (#2572) (@catloversg) - Add APIs to get rank gain and rank loss of an action (#2572) (@catloversg)
- Reduce RAM cost of inGang and inBladeburner APIs (#2582) (@catloversg) - Reduce RAM cost of inGang and inBladeburner APIs (#2582) (@catloversg)
- Fix skillMaxUpgradeCount returning 1 at extreme skill levels (#2611) (@lstutzman) - Fix skillMaxUpgradeCount returning 1 at extreme skill levels (#2611) (@lstutzman)
- API: Expose charged effects of Stanek's Gift active fragments (#2638) (@catloversg)
- Apply SF override to charisma calculations (#2642) (@catloversg)
- Update description of "BN9: Challenge" achievement (#2647) (@catloversg)
### SPOILER CHANGES - DOCUMENTATION ### SPOILER CHANGES - DOCUMENTATION
@@ -519,5 +538,9 @@ export const CONSTANTS = {
- Refactor and fix issues in db.ts (#2623) (@catloversg) - Refactor and fix issues in db.ts (#2623) (@catloversg)
- Add dependency array to TerminalInput keydown useEffect (#2620) (@lstutzman) - Add dependency array to TerminalInput keydown useEffect (#2620) (@lstutzman)
- Add dependency array to GameRoot useEffect (#2617) (@lstutzman) - Add dependency array to GameRoot useEffect (#2617) (@lstutzman)
- Dev menu: Initialize dark net data when setting SF15 level (#2632) (@catloversg)
- Use type-only imports in ArrayHelpers.ts (#2630) (@catloversg)
- Remove redundant "$" from JS/TS regex in webpack config (#2649) (@catloversg)
- Allow specifying commit hash id when building artifacts (#2652) (@catloversg)
`, `,
} as const; } as const;
+1 -1
View File
@@ -210,7 +210,7 @@ export const getTimingAttackConfig = (difficulty: number): ServerConfig => {
"I spent some time on it, but that's not the password", "I spent some time on it, but that's not the password",
]; ];
const alphanumeric = difficulty > 16 && Math.random() < 0.3; const alphanumeric = difficulty > 16 && Math.random() < 0.3;
const length = (alphanumeric ? 0 : 3) + difficulty / 4; const length = Math.min((alphanumeric ? 0 : 3) + difficulty / 4, 8);
return { return {
modelId: ModelIds.TimingAttack, modelId: ModelIds.TimingAttack,
password: getPassword(length, alphanumeric), password: getPassword(length, alphanumeric),
+1 -9
View File
@@ -79,17 +79,9 @@ export const calculateAuthenticationTime = (
const underleveledFactor = applyUnderleveledFactor ? 1.5 + (chaRequired + 50) / (person.skills.charisma + 50) : 1; const underleveledFactor = applyUnderleveledFactor ? 1.5 + (chaRequired + 50) / (person.skills.charisma + 50) : 1;
const hasBootsFactor = Player.hasAugmentation(AugmentationName.TheBoots) ? 0.8 : 1; const hasBootsFactor = Player.hasAugmentation(AugmentationName.TheBoots) ? 0.8 : 1;
const hasSf15_2Factor = Player.activeSourceFileLvl(15) > 2 ? 0.8 : 1; const hasSf15_2Factor = Player.activeSourceFileLvl(15) > 2 ? 0.8 : 1;
const bonusTimeFactor = hasDarknetBonusTime() ? 0.75 : 1;
const time = const time =
baseTime * baseTime * skillFactor * backdoorFactor * underleveledFactor * hasBootsFactor * hasSf15_2Factor * threadsFactor;
skillFactor *
backdoorFactor *
underleveledFactor *
hasBootsFactor *
hasSf15_2Factor *
bonusTimeFactor *
threadsFactor;
// We need to call GetServer and check if it's a dnet server later because this function can be called by formulas // We need to call GetServer and check if it's a dnet server later because this function can be called by formulas
// APIs (darknetServerData.hostname may be an invalid hostname). // APIs (darknetServerData.hostname may be an invalid hostname).
+7 -6
View File
@@ -1,6 +1,6 @@
import { Player } from "@player"; import { Player } from "@player";
import { addClue } from "./effects"; import { addClue } from "./effects";
import { formatNumber } from "../../ui/formatNumber"; import { formatNumber, formatRam } from "../../ui/formatNumber";
import { logger } from "./offlineServerHandling"; import { logger } from "./offlineServerHandling";
import type { NetscriptContext } from "../../Netscript/APIWrapper"; import type { NetscriptContext } from "../../Netscript/APIWrapper";
import type { DarknetServer } from "../../Server/DarknetServer"; import type { DarknetServer } from "../../Server/DarknetServer";
@@ -12,6 +12,7 @@ import type { DarknetServerData, Person as IPerson } from "@nsdefs";
import { clampNumber } from "../../utils/helpers/clampNumber"; import { clampNumber } from "../../utils/helpers/clampNumber";
import { ResponseCodeEnum } from "../Enums"; import { ResponseCodeEnum } from "../Enums";
import { isLabyrinthServer } from "./labyrinth"; import { isLabyrinthServer } from "./labyrinth";
import { roundToTwo } from "../../utils/helpers/roundToTwo";
/* /*
* Handles the effects of removing some blocked RAM from a Darknet server. * Handles the effects of removing some blocked RAM from a Darknet server.
@@ -21,7 +22,7 @@ export const handleRamBlockRemoved = (ctx: NetscriptContext, server: DarknetServ
const difficulty = server.difficulty + 1; const difficulty = server.difficulty + 1;
const ramBlockRemoved = getRamBlockRemoved(server, threads); const ramBlockRemoved = getRamBlockRemoved(server, threads);
server.blockedRam -= ramBlockRemoved; server.blockedRam = roundToTwo(server.blockedRam - ramBlockRemoved);
server.updateRamUsed(server.ramUsed - ramBlockRemoved); server.updateRamUsed(server.ramUsed - ramBlockRemoved);
if (server.blockedRam <= 0) { if (server.blockedRam <= 0) {
@@ -30,10 +31,10 @@ export const handleRamBlockRemoved = (ctx: NetscriptContext, server: DarknetServ
const xpGained = Player.mults.charisma_exp * threads * 10 * 1.1 ** difficulty; const xpGained = Player.mults.charisma_exp * threads * 10 * 1.1 ** difficulty;
Player.gainCharismaExp(xpGained); Player.gainCharismaExp(xpGained);
const result = `Liberated ${formatNumber( const result = `Liberated ${formatRam(
ramBlockRemoved, ramBlockRemoved,
4, 4,
)}gb of RAM from the server owner's processes. (Gained ${formatNumber(xpGained, 1)} cha xp.)`; )} of RAM from the server owner's processes. (Gained ${formatNumber(xpGained, 1)} cha xp.)`;
logger(ctx)(result); logger(ctx)(result);
return { return {
success: true, success: true,
@@ -72,7 +73,7 @@ export const getRamBlockRemoved = (darknetServerData: DarknetServerData, threads
const charismaFactor = 1 + player.skills.charisma / 100; const charismaFactor = 1 + player.skills.charisma / 100;
const difficultyFactor = 2 * 0.92 ** (difficulty + 1); const difficultyFactor = 2 * 0.92 ** (difficulty + 1);
const baseAmount = 0.02; const baseAmount = 0.02;
return clampNumber(baseAmount * difficultyFactor * threads * charismaFactor, 0, remainingRamBlock); return roundToTwo(clampNumber(baseAmount * difficultyFactor * threads * charismaFactor, 0, remainingRamBlock));
}; };
/* /*
@@ -100,5 +101,5 @@ export const getRamBlock = (maxRam: number): number => {
return [16, 32, maxRam - 8][Math.floor(Math.random() * 3)]; return [16, 32, maxRam - 8][Math.floor(Math.random() * 3)];
} }
return [maxRam, maxRam - 8, maxRam - 64, maxRam / 2][Math.floor(Math.random() * 4)]; return roundToTwo([maxRam, maxRam - 8, maxRam - 64, maxRam / 2][Math.floor(Math.random() * 4)]);
}; };
+1 -1
View File
@@ -77,7 +77,7 @@ export function NetworkDisplayWrapper(): React.ReactElement {
useEffect(() => { useEffect(() => {
const clearSubscription = DarknetEvents.subscribe(() => updateDisplay()); const clearSubscription = DarknetEvents.subscribe(() => updateDisplay());
draggableBackground.current?.addEventListener("wheel", (e) => e.preventDefault()); draggableBackground.current?.addEventListener("wheel", (e) => e.preventDefault(), { passive: false });
scrollTo(DarknetState.netViewTopScroll, DarknetState.netViewLeftScroll); scrollTo(DarknetState.netViewTopScroll, DarknetState.netViewLeftScroll);
updateDisplay(); updateDisplay();
@@ -367,12 +367,11 @@ Paste the following code into the [Script](../basic/scripts.md) editor:
/** @param {NS} ns */ /** @param {NS} ns */
export async function main(ns) { export async function main(ns) {
// How much RAM each cloud server will have. In this case, it'll // How much RAM each cloud server will have. In this case, it'll be 8GB.
// be 8GB.
const ram = 8; const ram = 8;
// Iterator we'll use for our loop // Iterator we'll use for our loop
let i = 0; let i = ns.cloud.getServerNames().length;
// Continuously try to purchase cloud servers until we've reached the maximum // Continuously try to purchase cloud servers until we've reached the maximum
// amount of servers // amount of servers
@@ -381,16 +380,16 @@ Paste the following code into the [Script](../basic/scripts.md) editor:
if (ns.getServerMoneyAvailable("home") > ns.cloud.getRamLimit(ram)) { if (ns.getServerMoneyAvailable("home") > ns.cloud.getRamLimit(ram)) {
// If we have enough money, then: // If we have enough money, then:
// 1. Purchase the server // 1. Purchase the server
// 2. Copy our hacking script onto the newly-purchased cloud server // 2. Copy our hacking script onto the newly purchased cloud server
// 3. Run our hacking script on the newly-purchased cloud server with 3 threads // 3. Run our hacking script on the newly purchased cloud server with 3 threads
// 4. Increment our iterator to indicate that we've bought a new server // 4. Increment our iterator to indicate that we've bought a new server
let hostname = ns.cloud.purchaseServer("cloud-server-" + i, ram); const hostname = ns.cloud.purchaseServer("cloud-server-" + i, ram);
ns.scp("early-hack-template.js", hostname); ns.scp("early-hack-template.js", hostname);
ns.exec("early-hack-template.js", hostname, 3); ns.exec("early-hack-template.js", hostname, 3);
++i; ++i;
} }
//Make the script wait for a second before looping again. // Make the script wait for a second before looping again.
//Removing this line will cause an infinite loop and crash the game. // Removing this line will cause an infinite loop and crash the game.
await ns.sleep(1000); await ns.sleep(1000);
} }
} }
@@ -33,12 +33,12 @@ Adding a sleep like in the first example, or changing the code so that the `awai
Common infinite loop when translating the server purchasing script in starting guide to scripts is to have a while loop, where the condition's change is conditional: Common infinite loop when translating the server purchasing script in starting guide to scripts is to have a while loop, where the condition's change is conditional:
var ram = 8; const ram = 8;
var i = 0; let i = ns.cloud.getServerNames().length;
while (i < ns.cloud.getServerLimit()) { while (i < ns.cloud.getServerLimit()) {
if (ns.getServerMoneyAvailable("home") > ns.cloud.getRamLimit(ram)) { if (ns.getServerMoneyAvailable("home") > ns.cloud.getRamLimit(ram)) {
var hostname = ns.cloud.purchaseServer("cloud-server-" + i, ram); const hostname = ns.cloud.purchaseServer("cloud-server-" + i, ram);
ns.scp("early-hack-template.js", hostname); ns.scp("early-hack-template.js", hostname);
ns.exec("early-hack-template.js", hostname, 3); ns.exec("early-hack-template.js", hostname, 3);
++i; ++i;
+4 -2
View File
@@ -561,6 +561,7 @@ import nsDoc_bitburner_gameinfo_versionnumber_md from "../../markdown/bitburner.
import nsDoc_bitburner_gang_ascendmember_md from "../../markdown/bitburner.gang.ascendmember.md?raw"; import nsDoc_bitburner_gang_ascendmember_md from "../../markdown/bitburner.gang.ascendmember.md?raw";
import nsDoc_bitburner_gang_canrecruitmember_md from "../../markdown/bitburner.gang.canrecruitmember.md?raw"; import nsDoc_bitburner_gang_canrecruitmember_md from "../../markdown/bitburner.gang.canrecruitmember.md?raw";
import nsDoc_bitburner_gang_creategang_md from "../../markdown/bitburner.gang.creategang.md?raw"; import nsDoc_bitburner_gang_creategang_md from "../../markdown/bitburner.gang.creategang.md?raw";
import nsDoc_bitburner_gang_getallganginformation_md from "../../markdown/bitburner.gang.getallganginformation.md?raw";
import nsDoc_bitburner_gang_getascensionresult_md from "../../markdown/bitburner.gang.getascensionresult.md?raw"; import nsDoc_bitburner_gang_getascensionresult_md from "../../markdown/bitburner.gang.getascensionresult.md?raw";
import nsDoc_bitburner_gang_getbonustime_md from "../../markdown/bitburner.gang.getbonustime.md?raw"; import nsDoc_bitburner_gang_getbonustime_md from "../../markdown/bitburner.gang.getbonustime.md?raw";
import nsDoc_bitburner_gang_getchancetowinclash_md from "../../markdown/bitburner.gang.getchancetowinclash.md?raw"; import nsDoc_bitburner_gang_getchancetowinclash_md from "../../markdown/bitburner.gang.getchancetowinclash.md?raw";
@@ -572,7 +573,6 @@ import nsDoc_bitburner_gang_getganginformation_md from "../../markdown/bitburner
import nsDoc_bitburner_gang_getinstallresult_md from "../../markdown/bitburner.gang.getinstallresult.md?raw"; import nsDoc_bitburner_gang_getinstallresult_md from "../../markdown/bitburner.gang.getinstallresult.md?raw";
import nsDoc_bitburner_gang_getmemberinformation_md from "../../markdown/bitburner.gang.getmemberinformation.md?raw"; import nsDoc_bitburner_gang_getmemberinformation_md from "../../markdown/bitburner.gang.getmemberinformation.md?raw";
import nsDoc_bitburner_gang_getmembernames_md from "../../markdown/bitburner.gang.getmembernames.md?raw"; import nsDoc_bitburner_gang_getmembernames_md from "../../markdown/bitburner.gang.getmembernames.md?raw";
import nsDoc_bitburner_gang_getotherganginformation_md from "../../markdown/bitburner.gang.getotherganginformation.md?raw";
import nsDoc_bitburner_gang_getrecruitsavailable_md from "../../markdown/bitburner.gang.getrecruitsavailable.md?raw"; import nsDoc_bitburner_gang_getrecruitsavailable_md from "../../markdown/bitburner.gang.getrecruitsavailable.md?raw";
import nsDoc_bitburner_gang_gettasknames_md from "../../markdown/bitburner.gang.gettasknames.md?raw"; import nsDoc_bitburner_gang_gettasknames_md from "../../markdown/bitburner.gang.gettasknames.md?raw";
import nsDoc_bitburner_gang_gettaskstats_md from "../../markdown/bitburner.gang.gettaskstats.md?raw"; import nsDoc_bitburner_gang_gettaskstats_md from "../../markdown/bitburner.gang.gettaskstats.md?raw";
@@ -746,6 +746,7 @@ import nsDoc_bitburner_hackingformulas_hackexp_md from "../../markdown/bitburner
import nsDoc_bitburner_hackingformulas_hackpercent_md from "../../markdown/bitburner.hackingformulas.hackpercent.md?raw"; import nsDoc_bitburner_hackingformulas_hackpercent_md from "../../markdown/bitburner.hackingformulas.hackpercent.md?raw";
import nsDoc_bitburner_hackingformulas_hacktime_md from "../../markdown/bitburner.hackingformulas.hacktime.md?raw"; import nsDoc_bitburner_hackingformulas_hacktime_md from "../../markdown/bitburner.hackingformulas.hacktime.md?raw";
import nsDoc_bitburner_hackingformulas_md from "../../markdown/bitburner.hackingformulas.md?raw"; import nsDoc_bitburner_hackingformulas_md from "../../markdown/bitburner.hackingformulas.md?raw";
import nsDoc_bitburner_hackingformulas_weakeneffect_md from "../../markdown/bitburner.hackingformulas.weakeneffect.md?raw";
import nsDoc_bitburner_hackingformulas_weakentime_md from "../../markdown/bitburner.hackingformulas.weakentime.md?raw"; import nsDoc_bitburner_hackingformulas_weakentime_md from "../../markdown/bitburner.hackingformulas.weakentime.md?raw";
import nsDoc_bitburner_hackingmultipliers_chance_md from "../../markdown/bitburner.hackingmultipliers.chance.md?raw"; import nsDoc_bitburner_hackingmultipliers_chance_md from "../../markdown/bitburner.hackingmultipliers.chance.md?raw";
import nsDoc_bitburner_hackingmultipliers_growth_md from "../../markdown/bitburner.hackingmultipliers.growth.md?raw"; import nsDoc_bitburner_hackingmultipliers_growth_md from "../../markdown/bitburner.hackingmultipliers.growth.md?raw";
@@ -2157,6 +2158,7 @@ AllPages["nsDoc/bitburner.gameinfo.versionnumber.md"] = nsDoc_bitburner_gameinfo
AllPages["nsDoc/bitburner.gang.ascendmember.md"] = nsDoc_bitburner_gang_ascendmember_md; AllPages["nsDoc/bitburner.gang.ascendmember.md"] = nsDoc_bitburner_gang_ascendmember_md;
AllPages["nsDoc/bitburner.gang.canrecruitmember.md"] = nsDoc_bitburner_gang_canrecruitmember_md; AllPages["nsDoc/bitburner.gang.canrecruitmember.md"] = nsDoc_bitburner_gang_canrecruitmember_md;
AllPages["nsDoc/bitburner.gang.creategang.md"] = nsDoc_bitburner_gang_creategang_md; AllPages["nsDoc/bitburner.gang.creategang.md"] = nsDoc_bitburner_gang_creategang_md;
AllPages["nsDoc/bitburner.gang.getallganginformation.md"] = nsDoc_bitburner_gang_getallganginformation_md;
AllPages["nsDoc/bitburner.gang.getascensionresult.md"] = nsDoc_bitburner_gang_getascensionresult_md; AllPages["nsDoc/bitburner.gang.getascensionresult.md"] = nsDoc_bitburner_gang_getascensionresult_md;
AllPages["nsDoc/bitburner.gang.getbonustime.md"] = nsDoc_bitburner_gang_getbonustime_md; AllPages["nsDoc/bitburner.gang.getbonustime.md"] = nsDoc_bitburner_gang_getbonustime_md;
AllPages["nsDoc/bitburner.gang.getchancetowinclash.md"] = nsDoc_bitburner_gang_getchancetowinclash_md; AllPages["nsDoc/bitburner.gang.getchancetowinclash.md"] = nsDoc_bitburner_gang_getchancetowinclash_md;
@@ -2168,7 +2170,6 @@ AllPages["nsDoc/bitburner.gang.getganginformation.md"] = nsDoc_bitburner_gang_ge
AllPages["nsDoc/bitburner.gang.getinstallresult.md"] = nsDoc_bitburner_gang_getinstallresult_md; AllPages["nsDoc/bitburner.gang.getinstallresult.md"] = nsDoc_bitburner_gang_getinstallresult_md;
AllPages["nsDoc/bitburner.gang.getmemberinformation.md"] = nsDoc_bitburner_gang_getmemberinformation_md; AllPages["nsDoc/bitburner.gang.getmemberinformation.md"] = nsDoc_bitburner_gang_getmemberinformation_md;
AllPages["nsDoc/bitburner.gang.getmembernames.md"] = nsDoc_bitburner_gang_getmembernames_md; AllPages["nsDoc/bitburner.gang.getmembernames.md"] = nsDoc_bitburner_gang_getmembernames_md;
AllPages["nsDoc/bitburner.gang.getotherganginformation.md"] = nsDoc_bitburner_gang_getotherganginformation_md;
AllPages["nsDoc/bitburner.gang.getrecruitsavailable.md"] = nsDoc_bitburner_gang_getrecruitsavailable_md; AllPages["nsDoc/bitburner.gang.getrecruitsavailable.md"] = nsDoc_bitburner_gang_getrecruitsavailable_md;
AllPages["nsDoc/bitburner.gang.gettasknames.md"] = nsDoc_bitburner_gang_gettasknames_md; AllPages["nsDoc/bitburner.gang.gettasknames.md"] = nsDoc_bitburner_gang_gettasknames_md;
AllPages["nsDoc/bitburner.gang.gettaskstats.md"] = nsDoc_bitburner_gang_gettaskstats_md; AllPages["nsDoc/bitburner.gang.gettaskstats.md"] = nsDoc_bitburner_gang_gettaskstats_md;
@@ -2342,6 +2343,7 @@ AllPages["nsDoc/bitburner.hackingformulas.hackexp.md"] = nsDoc_bitburner_hacking
AllPages["nsDoc/bitburner.hackingformulas.hackpercent.md"] = nsDoc_bitburner_hackingformulas_hackpercent_md; AllPages["nsDoc/bitburner.hackingformulas.hackpercent.md"] = nsDoc_bitburner_hackingformulas_hackpercent_md;
AllPages["nsDoc/bitburner.hackingformulas.hacktime.md"] = nsDoc_bitburner_hackingformulas_hacktime_md; AllPages["nsDoc/bitburner.hackingformulas.hacktime.md"] = nsDoc_bitburner_hackingformulas_hacktime_md;
AllPages["nsDoc/bitburner.hackingformulas.md"] = nsDoc_bitburner_hackingformulas_md; AllPages["nsDoc/bitburner.hackingformulas.md"] = nsDoc_bitburner_hackingformulas_md;
AllPages["nsDoc/bitburner.hackingformulas.weakeneffect.md"] = nsDoc_bitburner_hackingformulas_weakeneffect_md;
AllPages["nsDoc/bitburner.hackingformulas.weakentime.md"] = nsDoc_bitburner_hackingformulas_weakentime_md; AllPages["nsDoc/bitburner.hackingformulas.weakentime.md"] = nsDoc_bitburner_hackingformulas_weakentime_md;
AllPages["nsDoc/bitburner.hackingmultipliers.chance.md"] = nsDoc_bitburner_hackingmultipliers_chance_md; AllPages["nsDoc/bitburner.hackingmultipliers.chance.md"] = nsDoc_bitburner_hackingmultipliers_chance_md;
AllPages["nsDoc/bitburner.hackingmultipliers.growth.md"] = nsDoc_bitburner_hackingmultipliers_growth_md; AllPages["nsDoc/bitburner.hackingmultipliers.growth.md"] = nsDoc_bitburner_hackingmultipliers_growth_md;
+25 -6
View File
@@ -9,6 +9,10 @@ import Tabs from "@mui/material/Tabs";
import Tab from "@mui/material/Tab"; import Tab from "@mui/material/Tab";
import { useCycleRerender } from "../../ui/React/hooks"; import { useCycleRerender } from "../../ui/React/hooks";
import Button from "@mui/material/Button";
import { Router } from "../../ui/GameRoot";
import { Page } from "../../ui/Router";
import { Factions } from "../../Faction/Factions";
/** React Component for all the gang stuff. */ /** React Component for all the gang stuff. */
export function GangRoot(): React.ReactElement { export function GangRoot(): React.ReactElement {
@@ -18,7 +22,7 @@ export function GangRoot(): React.ReactElement {
})(); })();
const [value, setValue] = React.useState(0); const [value, setValue] = React.useState(0);
function handleChange(event: React.SyntheticEvent, tab: number): void { function handleChange(__event: React.SyntheticEvent, tab: number): void {
setValue(tab); setValue(tab);
} }
@@ -26,11 +30,26 @@ export function GangRoot(): React.ReactElement {
return ( return (
<Context.Gang.Provider value={gang}> <Context.Gang.Provider value={gang}>
<Tabs variant="fullWidth" value={value} onChange={handleChange} sx={{ minWidth: "fit-content", maxWidth: "45%" }}> <div style={{ display: "flex" }}>
<Tab label="Management" /> <Tabs
<Tab label="Equipment" /> variant="fullWidth"
<Tab label="Territory" /> value={value}
</Tabs> onChange={handleChange}
sx={{ minWidth: "fit-content", maxWidth: "45%" }}
>
<Tab label="Management" />
<Tab label="Equipment" />
<Tab label="Territory" />
</Tabs>
<Button
style={{ marginLeft: "20px" }}
onClick={() => {
Router.toPage(Page.Faction, { faction: Factions[gang.facName] });
}}
>
Faction
</Button>
</div>
{value === 0 && <ManagementSubpage />} {value === 0 && <ManagementSubpage />}
{value === 1 && <EquipmentsSubpage />} {value === 1 && <EquipmentsSubpage />}
{value === 2 && <TerritorySubpage />} {value === 2 && <TerritorySubpage />}
+24 -24
View File
@@ -4,37 +4,37 @@ let pidCounter = 1;
/** Find and return the next available PID for a script */ /** Find and return the next available PID for a script */
export function generateNextPid(): number { export function generateNextPid(): number {
let tempCounter = pidCounter; let pidCandidate = pidCounter;
// Cap the number of search iterations at some arbitrary value to avoid // Cap the number of search iterations at some arbitrary value to avoid
// infinite loops. We'll assume that players wont have 1mil+ running scripts // infinite loops. We'll assume that players won't have a million running scripts.
let found = false; for (let attemptCounter = 0; attemptCounter < 1e6; ++attemptCounter, ++pidCandidate) {
for (let i = 0; i < 1e6; ) { // ensure the candidate PID is a safe integer
if (!workerScripts.has(tempCounter + i)) { if (pidCandidate >= Number.MAX_SAFE_INTEGER) {
found = true; pidCandidate = 1;
tempCounter = tempCounter + i;
break;
} }
// ensure the PID is not in use
if (i === Number.MAX_SAFE_INTEGER - 1) { if (workerScripts.has(pidCandidate)) {
i = 1; continue;
} else {
++i;
} }
// found a PID that's not in use
pidCounter = pidCandidate + 1;
return pidCandidate;
} }
// ran out of attempts without finding an unused PID :-(
if (found) { return -1;
pidCounter = tempCounter + 1;
if (pidCounter >= Number.MAX_SAFE_INTEGER) {
pidCounter = 1;
}
return tempCounter;
} else {
return -1;
}
} }
/**
* Reset the PID counter to 1.
*
* Note that the list of recently finished scripts has to be
* cleared (`recentScripts.splice(0)`) when resetting the PID counter.
* Otherwise scripts which re-use a PID that is still in the
* list of recent scripts do not show up there when they finish
* (if the previous script with that PID is still in the list at that point),
* because the function `AddRecentScript` de-duplicates scripts by PID.
*/
export function resetPidCounter(): void { export function resetPidCounter(): void {
pidCounter = 1; pidCounter = 1;
} }
+2 -1
View File
@@ -275,7 +275,7 @@ const gang = {
getMemberNames: RamCostConstants.GangApiBase / 4, getMemberNames: RamCostConstants.GangApiBase / 4,
renameMember: 0, renameMember: 0,
getGangInformation: RamCostConstants.GangApiBase / 2, getGangInformation: RamCostConstants.GangApiBase / 2,
getOtherGangInformation: RamCostConstants.GangApiBase / 2, getAllGangInformation: RamCostConstants.GangApiBase / 2,
getMemberInformation: RamCostConstants.GangApiBase / 2, getMemberInformation: RamCostConstants.GangApiBase / 2,
canRecruitMember: RamCostConstants.GangApiBase / 4, canRecruitMember: RamCostConstants.GangApiBase / 4,
getRecruitsAvailable: RamCostConstants.GangApiBase / 4, getRecruitsAvailable: RamCostConstants.GangApiBase / 4,
@@ -692,6 +692,7 @@ export const RamCosts: RamCostTree<NSFull> = {
hackTime: 0, hackTime: 0,
growTime: 0, growTime: 0,
weakenTime: 0, weakenTime: 0,
weakenEffect: 0,
}, },
hacknetNodes: { hacknetNodes: {
moneyGainRate: 0, moneyGainRate: 0,
+7 -6
View File
@@ -16,7 +16,7 @@ import { helpers } from "../Netscript/NetscriptHelpers";
import { getEnumHelper } from "../utils/EnumHelper"; import { getEnumHelper } from "../utils/EnumHelper";
import { Skills } from "../Bladeburner/data/Skills"; import { Skills } from "../Bladeburner/data/Skills";
import { assertStringWithNSContext } from "../Netscript/TypeAssertion"; import { assertStringWithNSContext } from "../Netscript/TypeAssertion";
import { BlackOperations, blackOpsArray } from "../Bladeburner/data/BlackOperations"; import { numberOfBlackOperations } from "../Bladeburner/data/BlackOperations";
import { checkSleeveAPIAccess, checkSleeveNumber } from "../NetscriptFunctions/Sleeve"; import { checkSleeveAPIAccess, checkSleeveNumber } from "../NetscriptFunctions/Sleeve";
import { canAccessBitNodeFeature } from "../BitNode/BitNodeUtils"; import { canAccessBitNodeFeature } from "../BitNode/BitNodeUtils";
import { import {
@@ -81,20 +81,21 @@ export function NetscriptBladeburner(): InternalAPI<INetscriptBladeburner> {
return Object.values(BladeburnerOperationName); return Object.values(BladeburnerOperationName);
}, },
getBlackOpNames: (ctx) => () => { getBlackOpNames: (ctx) => () => {
getBladeburner(ctx); const bladeburner = getBladeburner(ctx);
// Ensures they are sent in the correct order // Ensures they are sent in the correct order
return blackOpsArray.map((blackOp) => blackOp.name); return bladeburner.blackOperationArray.map((blackOp) => blackOp.name);
}, },
getNextBlackOp: (ctx) => () => { getNextBlackOp: (ctx) => () => {
const bladeburner = getBladeburner(ctx); const bladeburner = getBladeburner(ctx);
if (bladeburner.numBlackOpsComplete >= blackOpsArray.length) return null; if (bladeburner.numBlackOpsComplete >= numberOfBlackOperations) return null;
const blackOp = blackOpsArray[bladeburner.numBlackOpsComplete]; const blackOp = bladeburner.blackOperationArray[bladeburner.numBlackOpsComplete];
return { name: blackOp.name, rank: blackOp.reqdRank }; return { name: blackOp.name, rank: blackOp.reqdRank };
}, },
getBlackOpRank: (ctx) => (_blackOpName) => { getBlackOpRank: (ctx) => (_blackOpName) => {
checkBladeburnerAccess(ctx); checkBladeburnerAccess(ctx);
const blackOpName = getEnumHelper("BladeburnerBlackOpName").nsGetMember(ctx, _blackOpName); const blackOpName = getEnumHelper("BladeburnerBlackOpName").nsGetMember(ctx, _blackOpName);
return BlackOperations[blackOpName].reqdRank; const bladeburner = getBladeburner(ctx);
return bladeburner.blackOperations[blackOpName].reqdRank;
}, },
getGeneralActionNames: (ctx) => () => { getGeneralActionNames: (ctx) => () => {
getBladeburner(ctx); getBladeburner(ctx);
+9 -1
View File
@@ -1,6 +1,6 @@
import { Player } from "@player"; import { Player } from "@player";
import { calculateServerGrowth, calculateGrowMoney } from "../Server/formulas/grow"; import { calculateServerGrowth, calculateGrowMoney } from "../Server/formulas/grow";
import { numCycleForGrowthCorrected } from "../Server/ServerHelpers"; import { getWeakenEffect, numCycleForGrowthCorrected } from "../Server/ServerHelpers";
import { import {
calculateMoneyGainRate, calculateMoneyGainRate,
calculateLevelUpgradeCost, calculateLevelUpgradeCost,
@@ -235,6 +235,14 @@ export function NetscriptFormulas(): InternalAPI<IFormulas> {
checkFormulasAccess(ctx); checkFormulasAccess(ctx);
return calculateWeakenTime(server, person) * 1000; return calculateWeakenTime(server, person) * 1000;
}, },
weakenEffect:
(ctx) =>
(_threads, _cores = 1) => {
const threads = helpers.number(ctx, "threads", _threads);
const cores = helpers.number(ctx, "cores", _cores);
checkFormulasAccess(ctx);
return getWeakenEffect(threads, cores);
},
}, },
hacknetNodes: { hacknetNodes: {
moneyGainRate: moneyGainRate:
+9 -3
View File
@@ -2,7 +2,7 @@ import type { Gang as IGang, EquipmentStats, GangOtherInfoObject } from "@nsdefs
import type { Gang } from "../Gang/Gang"; import type { Gang } from "../Gang/Gang";
import type { GangMember } from "../Gang/GangMember"; import type { GangMember } from "../Gang/GangMember";
import type { GangMemberTask } from "../Gang/GangMemberTask"; import type { GangMemberTask } from "../Gang/GangMemberTask";
import type { InternalAPI, NetscriptContext } from "../Netscript/APIWrapper"; import { type InternalAPI, type NetscriptContext, setRemovedFunctions } from "../Netscript/APIWrapper";
import { GangPromise, RecruitmentResult } from "../Gang/Gang"; import { GangPromise, RecruitmentResult } from "../Gang/Gang";
import { Player } from "@player"; import { Player } from "@player";
@@ -37,7 +37,7 @@ export function NetscriptGang(): InternalAPI<IGang> {
return task; return task;
}; };
return { const gangFunctions: InternalAPI<IGang> = {
createGang: (ctx) => (_faction) => { createGang: (ctx) => (_faction) => {
const faction = getEnumHelper("FactionName").nsGetMember(ctx, _faction); const faction = getEnumHelper("FactionName").nsGetMember(ctx, _faction);
if (Player.gang) { if (Player.gang) {
@@ -117,7 +117,7 @@ export function NetscriptGang(): InternalAPI<IGang> {
equipmentCostMult: 1 / gang.getDiscount(), equipmentCostMult: 1 / gang.getDiscount(),
}; };
}, },
getOtherGangInformation: (ctx) => () => { getAllGangInformation: (ctx) => () => {
getGang(ctx); getGang(ctx);
const cpy: Record<string, GangOtherInfoObject> = {}; const cpy: Record<string, GangOtherInfoObject> = {};
for (const gang of Object.keys(AllGangs)) { for (const gang of Object.keys(AllGangs)) {
@@ -362,4 +362,10 @@ export function NetscriptGang(): InternalAPI<IGang> {
return GangPromise.promise; return GangPromise.promise;
}, },
}; };
// Removed functions
setRemovedFunctions(gangFunctions, {
getOtherGangInformation: { version: "3.0.0", replacement: "gang.getAllGangInformation" },
});
return gangFunctions;
} }
+2 -2
View File
@@ -45,7 +45,7 @@ import { ScriptFilePath, resolveScriptFilePath } from "../Paths/ScriptFilePath";
import { getRecordEntries } from "../Types/Record"; import { getRecordEntries } from "../Types/Record";
import { JobTracks } from "../Company/data/JobTracks"; import { JobTracks } from "../Company/data/JobTracks";
import { ServerConstants } from "../Server/data/Constants"; import { ServerConstants } from "../Server/data/Constants";
import { blackOpsArray } from "../Bladeburner/data/BlackOperations"; import { numberOfBlackOperations } from "../Bladeburner/data/BlackOperations";
import { calculateEffectiveRequiredReputation } from "../Company/utils"; import { calculateEffectiveRequiredReputation } from "../Company/utils";
import { addRepToFavor } from "../Faction/formulas/favor"; import { addRepToFavor } from "../Faction/formulas/favor";
import { validBitNodes } from "../BitNode/Constants"; import { validBitNodes } from "../BitNode/Constants";
@@ -1176,7 +1176,7 @@ export function NetscriptSingularity(): InternalAPI<ISingularity> {
if (!Player.bladeburner) { if (!Player.bladeburner) {
return false; return false;
} }
return Player.bladeburner.numBlackOpsComplete >= blackOpsArray.length; return Player.bladeburner.numBlackOpsComplete >= numberOfBlackOperations;
}; };
if (!hackingRequirements() && !bladeburnerRequirements()) { if (!hackingRequirements() && !bladeburnerRequirements()) {
+11 -1
View File
@@ -147,6 +147,16 @@ export abstract class Person implements IPerson {
} }
overrideIntelligence(): void { overrideIntelligence(): void {
// Reset intelligence data if the player has not unlocked Intelligence.
// Note that this check cannot reset intelligence data in some edge cases (e.g., bitflume from non-BN5 to BN5). This
// is an accepted limitation.
// For more information, please check https://github.com/bitburner-official/bitburner-src/pull/2666
if (Player.sourceFileLvl(5) === 0 && Player.bitNodeN !== 5) {
this.skills.intelligence = 0;
this.exp.intelligence = 0;
this.persistentIntelligenceData.exp = 0;
return;
}
const persistentIntelligenceSkill = this.calculateSkill(this.persistentIntelligenceData.exp, 1); const persistentIntelligenceSkill = this.calculateSkill(this.persistentIntelligenceData.exp, 1);
// Reset exp and skill to the persistent values if there is no limit (intelligenceOverride) or the limit is greater // Reset exp and skill to the persistent values if there is no limit (intelligenceOverride) or the limit is greater
// than or equal to the persistent skill. // than or equal to the persistent skill.
@@ -172,7 +182,7 @@ export abstract class Person implements IPerson {
* Don't change sourceFileLvl to activeSourceFileLvl. When the player has int level, the ability to gain more int is * Don't change sourceFileLvl to activeSourceFileLvl. When the player has int level, the ability to gain more int is
* a permanent benefit. * a permanent benefit.
*/ */
if (Player.sourceFileLvl(5) > 0 || this.skills.intelligence > 0 || Player.bitNodeN === 5) { if (Player.sourceFileLvl(5) > 0 || Player.bitNodeN === 5) {
this.exp.intelligence += exp; this.exp.intelligence += exp;
this.skills.intelligence = Math.floor(this.calculateSkill(this.exp.intelligence, 1)); this.skills.intelligence = Math.floor(this.calculateSkill(this.exp.intelligence, 1));
this.persistentIntelligenceData.exp += exp; this.persistentIntelligenceData.exp += exp;
@@ -135,12 +135,14 @@ export function prestigeAugmentation(this: PlayerObject): void {
this.hp.current = this.hp.max; this.hp.current = this.hp.max;
this.finishWork(true, true); this.finishWork(true, true);
// We need to call overrideIntelligence here instead of prestigeSourceFile to reset intelligence data when installing
// augmentations.
this.overrideIntelligence();
} }
export function prestigeSourceFile(this: PlayerObject): void { export function prestigeSourceFile(this: PlayerObject): void {
this.entropy = 0; this.entropy = 0;
this.prestigeAugmentation(); this.prestigeAugmentation();
this.overrideIntelligence();
this.karma = 0; this.karma = 0;
// Duplicate sleeves are reset to level 1 every Bit Node (but the number of sleeves you have persists) // Duplicate sleeves are reset to level 1 every Bit Node (but the number of sleeves you have persists)
this.sleeves.forEach((sleeve) => sleeve.prestige()); this.sleeves.forEach((sleeve) => sleeve.prestige());
@@ -602,6 +604,10 @@ export function canAccessCotMG(this: PlayerObject): boolean {
return canAccessBitNodeFeature(13); return canAccessBitNodeFeature(13);
} }
/**
* To ensure the "SF override" option work properly, this function should only be used in special cases. In most cases,
* activeSourceFileLvl should be used instead.
*/
export function sourceFileLvl(this: PlayerObject, n: number): number { export function sourceFileLvl(this: PlayerObject, n: number): number {
return this.sourceFiles.get(n) ?? 0; return this.sourceFiles.get(n) ?? 0;
} }
+1 -1
View File
@@ -52,7 +52,7 @@ export function getFactionFieldWorkRepGain(p: IPerson, favor: number): number {
} }
function getDarknetCharismaBonus(p: IPerson, scalar: number = 1): number { function getDarknetCharismaBonus(p: IPerson, scalar: number = 1): number {
if (Player.sourceFileLvl(15) >= 3) { if (Player.activeSourceFileLvl(15) >= 3) {
return p.skills.charisma * scalar; return p.skills.charisma * scalar;
} }
return 0; return 0;
+6 -1
View File
@@ -5,6 +5,11 @@ import { clampNumber } from "../../utils/helpers/clampNumber";
* stat level. Stat-agnostic (same formula for every stat) * stat level. Stat-agnostic (same formula for every stat)
*/ */
export function calculateSkill(exp: number, mult = 1): number { export function calculateSkill(exp: number, mult = 1): number {
// Mult can be 0 in BN12 when the player has a very high SF12 level. In this case, the skill level will never change
// from its initial value (1 for most stats, except intelligence).
if (mult === 0) {
return 1;
}
const value = Math.floor(mult * (32 * Math.log(exp + 534.6) - 200)); const value = Math.floor(mult * (32 * Math.log(exp + 534.6) - 200));
return clampNumber(value, 1); return clampNumber(value, 1);
} }
@@ -12,7 +17,7 @@ export function calculateSkill(exp: number, mult = 1): number {
export function calculateExp(skill: number, mult = 1): number { export function calculateExp(skill: number, mult = 1): number {
const floorSkill = Math.floor(skill); const floorSkill = Math.floor(skill);
let value = Math.exp((skill / mult + 200) / 32) - 534.6; let value = Math.exp((skill / mult + 200) / 32) - 534.6;
if (skill === floorSkill && Number.isFinite(skill)) { if (skill === floorSkill && Number.isFinite(skill) && Number.isFinite(value)) {
// Check for floating point rounding issues that would cause the inverse // Check for floating point rounding issues that would cause the inverse
// operation to return the wrong result. // operation to return the wrong result.
let calcSkill = calculateSkill(value, mult); let calcSkill = calculateSkill(value, mult);
+3 -1
View File
@@ -190,6 +190,8 @@ export function prestigeAugmentation(): void {
} }
} }
// clear recent scripts
recentScripts.splice(0);
resetPidCounter(); resetPidCounter();
ProgramsSeen.clear(); ProgramsSeen.clear();
InvitationsSeen.clear(); InvitationsSeen.clear();
@@ -310,7 +312,7 @@ export function prestigeSourceFile(isFlume: boolean): void {
} }
// BitNode 12: The Recursion // BitNode 12: The Recursion
if (Player.bitNodeN === 12 && Player.activeSourceFileLvl(12) > 100) { if (Player.bitNodeN === 12 && Player.sourceFileLvl(12) > 100) {
delayedDialog("Saynt_Garmo is watching you"); delayedDialog("Saynt_Garmo is watching you");
} }
-10
View File
@@ -35,9 +35,6 @@ function giveSourceFile(bitNodeNumber: number): void {
} }
} else { } else {
Player.sourceFiles.set(bitNodeNumber, 1); Player.sourceFiles.set(bitNodeNumber, 1);
if (bitNodeNumber === 5 && Player.skills.intelligence === 0) {
Player.skills.intelligence = 1;
}
dialogBoxCreate( dialogBoxCreate(
<> <>
You received a Source-File for destroying a BitNode! You received a Source-File for destroying a BitNode!
@@ -63,13 +60,6 @@ export function enterBitNode(
if (!isFlume) { if (!isFlume) {
giveSourceFile(destroyedBitNode); giveSourceFile(destroyedBitNode);
} else if (Player.sourceFileLvl(5) === 0 && newBitNode !== 5) {
Player.skills.intelligence = 0;
Player.exp.intelligence = 0;
Player.persistentIntelligenceData.exp = 0;
}
if (newBitNode === 5 && Player.skills.intelligence === 0) {
Player.skills.intelligence = 1;
} }
// Set new Bit Node // Set new Bit Node
Player.bitNodeN = newBitNode; Player.bitNodeN = newBitNode;
+6 -2
View File
@@ -36,6 +36,7 @@ import { loadInfiltrations } from "./Infiltration/SaveLoadInfiltration";
import { InfiltrationState } from "./Infiltration/formulas/game"; import { InfiltrationState } from "./Infiltration/formulas/game";
import { hasDarknetAccess } from "./DarkNet/utils/darknetAuthUtils"; import { hasDarknetAccess } from "./DarkNet/utils/darknetAuthUtils";
import { loadSettings } from "./Settings/SettingsUtils"; import { loadSettings } from "./Settings/SettingsUtils";
import { getBitNodeLevel } from "./BitNode/BitNodeUtils";
/* SaveObject.js /* SaveObject.js
* Defines the object used to save/load games * Defines the object used to save/load games
@@ -270,7 +271,7 @@ class BitburnerSaveObject implements BitburnerSaveObjectType {
* - Base64 format: save file uses .json extension. Save data is the base64-encoded json save string. * - Base64 format: save file uses .json extension. Save data is the base64-encoded json save string.
*/ */
const extension = canUseBinaryFormat() ? "json.gz" : "json"; const extension = canUseBinaryFormat() ? "json.gz" : "json";
return `bitburnerSave_${epochTime}_BN${bn}x${Player.sourceFileLvl(bn) + 1}.${extension}`; return `bitburnerSave_${epochTime}_BN${bn}x${getBitNodeLevel()}.${extension}`;
} }
async exportGame(): Promise<void> { async exportGame(): Promise<void> {
@@ -430,7 +431,10 @@ class BitburnerSaveObject implements BitburnerSaveObjectType {
achievements: importedPlayer.achievements?.length ?? 0, achievements: importedPlayer.achievements?.length ?? 0,
bitNode: importedPlayer.bitNodeN, bitNode: importedPlayer.bitNodeN,
bitNodeLevel: importedPlayer.sourceFileLvl(importedPlayer.bitNodeN) + 1, bitNodeLevel: getBitNodeLevel(
importedPlayer.bitNodeN,
importedPlayer.activeSourceFileLvl(importedPlayer.bitNodeN),
),
sourceFiles: [...importedPlayer.sourceFiles].reduce<number>((total, [__bn, lvl]) => (total += lvl), 0), sourceFiles: [...importedPlayer.sourceFiles].reduce<number>((total, [__bn, lvl]) => (total += lvl), 0),
exploits: importedPlayer.exploits.length, exploits: importedPlayer.exploits.length,
+44 -45
View File
@@ -1852,7 +1852,7 @@ export type Task = StudyTask | CompanyWorkTask | CreateProgramWorkTask | CrimeTa
* *
* - All boolean options: false * - All boolean options: false
* *
* If you specify intelligenceOverride, it must be a non-negative integer. * If you specify intelligenceOverride, it must be a positive integer.
* *
* @public * @public
*/ */
@@ -2925,7 +2925,7 @@ export interface Hacknet {
/** /**
* Get the number of hacknet nodes you own. * Get the number of hacknet nodes you own.
* @remarks * @remarks
* RAM cost: 0 GB * RAM cost: 0.5 GB
* *
* Returns the number of Hacknet Nodes you own. * Returns the number of Hacknet Nodes you own.
* *
@@ -2936,7 +2936,7 @@ export interface Hacknet {
/** /**
* Get the maximum number of hacknet nodes. * Get the maximum number of hacknet nodes.
* @remarks * @remarks
* RAM cost: 0 GB * RAM cost: 0.5 GB
* *
* @returns Maximum number of hacknet nodes. * @returns Maximum number of hacknet nodes.
*/ */
@@ -2945,7 +2945,7 @@ export interface Hacknet {
/** /**
* Purchase a new hacknet node. * Purchase a new hacknet node.
* @remarks * @remarks
* RAM cost: 0 GB * RAM cost: 0.5 GB
* *
* Purchases a new Hacknet Node. Returns a number with the index of the * Purchases a new Hacknet Node. Returns a number with the index of the
* Hacknet Node. This index is equivalent to the number at the end of * Hacknet Node. This index is equivalent to the number at the end of
@@ -2961,7 +2961,7 @@ export interface Hacknet {
/** /**
* Get the price of the next hacknet node. * Get the price of the next hacknet node.
* @remarks * @remarks
* RAM cost: 0 GB * RAM cost: 0.5 GB
* *
* Returns the cost of purchasing a new Hacknet Node. * Returns the cost of purchasing a new Hacknet Node.
* *
@@ -2972,7 +2972,7 @@ export interface Hacknet {
/** /**
* Get the stats of a hacknet node. * Get the stats of a hacknet node.
* @remarks * @remarks
* RAM cost: 0 GB * RAM cost: 0.5 GB
* *
* Returns an object containing a variety of stats about the specified Hacknet Node. * Returns an object containing a variety of stats about the specified Hacknet Node.
* *
@@ -2988,7 +2988,7 @@ export interface Hacknet {
/** /**
* Upgrade the level of a hacknet node. * Upgrade the level of a hacknet node.
* @remarks * @remarks
* RAM cost: 0 GB * RAM cost: 0.5 GB
* *
* Tries to upgrade the level of the specified Hacknet Node by n. * Tries to upgrade the level of the specified Hacknet Node by n.
* *
@@ -3006,7 +3006,7 @@ export interface Hacknet {
/** /**
* Upgrade the RAM of a hacknet node. * Upgrade the RAM of a hacknet node.
* @remarks * @remarks
* RAM cost: 0 GB * RAM cost: 0.5 GB
* *
* Tries to upgrade the specified Hacknet Nodes RAM n times. * Tries to upgrade the specified Hacknet Nodes RAM n times.
* Note that each upgrade doubles the Nodes RAM. * Note that each upgrade doubles the Nodes RAM.
@@ -3026,7 +3026,7 @@ export interface Hacknet {
/** /**
* Upgrade the core of a hacknet node. * Upgrade the core of a hacknet node.
* @remarks * @remarks
* RAM cost: 0 GB * RAM cost: 0.5 GB
* *
* Tries to purchase n cores for the specified Hacknet Node. * Tries to purchase n cores for the specified Hacknet Node.
* *
@@ -3044,7 +3044,7 @@ export interface Hacknet {
/** /**
* Upgrade the cache of a hacknet node. * Upgrade the cache of a hacknet node.
* @remarks * @remarks
* RAM cost: 0 GB * RAM cost: 0.5 GB
* *
* This function is only applicable for Hacknet Servers (the upgraded version of a Hacknet Node). * This function is only applicable for Hacknet Servers (the upgraded version of a Hacknet Node).
* *
@@ -3064,7 +3064,7 @@ export interface Hacknet {
/** /**
* Calculate the cost of upgrading hacknet node levels. * Calculate the cost of upgrading hacknet node levels.
* @remarks * @remarks
* RAM cost: 0 GB * RAM cost: 0.5 GB
* *
* Returns the cost of upgrading the specified Hacknet Node by n levels. * Returns the cost of upgrading the specified Hacknet Node by n levels.
* *
@@ -3080,7 +3080,7 @@ export interface Hacknet {
/** /**
* Calculate the cost of upgrading hacknet node RAM. * Calculate the cost of upgrading hacknet node RAM.
* @remarks * @remarks
* RAM cost: 0 GB * RAM cost: 0.5 GB
* *
* Returns the cost of upgrading the RAM of the specified Hacknet Node n times. * Returns the cost of upgrading the RAM of the specified Hacknet Node n times.
* *
@@ -3096,7 +3096,7 @@ export interface Hacknet {
/** /**
* Calculate the cost of upgrading hacknet node cores. * Calculate the cost of upgrading hacknet node cores.
* @remarks * @remarks
* RAM cost: 0 GB * RAM cost: 0.5 GB
* *
* Returns the cost of upgrading the number of cores of the specified Hacknet Node by n. * Returns the cost of upgrading the number of cores of the specified Hacknet Node by n.
* *
@@ -3112,7 +3112,7 @@ export interface Hacknet {
/** /**
* Calculate the cost of upgrading hacknet node cache. * Calculate the cost of upgrading hacknet node cache.
* @remarks * @remarks
* RAM cost: 0 GB * RAM cost: 0.5 GB
* *
* This function is only applicable for Hacknet Servers (the upgraded version of a Hacknet Node). * This function is only applicable for Hacknet Servers (the upgraded version of a Hacknet Node).
* *
@@ -3130,7 +3130,7 @@ export interface Hacknet {
/** /**
* Get the total number of hashes stored. * Get the total number of hashes stored.
* @remarks * @remarks
* RAM cost: 0 GB * RAM cost: 0.5 GB
* *
* This function is only applicable for Hacknet Servers (the upgraded version of a Hacknet Node). * This function is only applicable for Hacknet Servers (the upgraded version of a Hacknet Node).
* *
@@ -3143,7 +3143,7 @@ export interface Hacknet {
/** /**
* Get the maximum number of hashes you can store. * Get the maximum number of hashes you can store.
* @remarks * @remarks
* RAM cost: 0 GB * RAM cost: 0.5 GB
* *
* This function is only applicable for Hacknet Servers (the upgraded version of a Hacknet Node). * This function is only applicable for Hacknet Servers (the upgraded version of a Hacknet Node).
* *
@@ -3156,7 +3156,7 @@ export interface Hacknet {
/** /**
* Get the cost of a hash upgrade. * Get the cost of a hash upgrade.
* @remarks * @remarks
* RAM cost: 0 GB * RAM cost: 0.5 GB
* *
* This function is only applicable for Hacknet Servers (the upgraded version of a Hacknet Node). * This function is only applicable for Hacknet Servers (the upgraded version of a Hacknet Node).
* *
@@ -3178,7 +3178,7 @@ export interface Hacknet {
/** /**
* Purchase a hash upgrade. * Purchase a hash upgrade.
* @remarks * @remarks
* RAM cost: 0 GB * RAM cost: 0.5 GB
* *
* This function is only applicable for Hacknet Servers (the upgraded version of a Hacknet Node). * This function is only applicable for Hacknet Servers (the upgraded version of a Hacknet Node).
* *
@@ -3207,7 +3207,7 @@ export interface Hacknet {
/** /**
* Get the list of hash upgrades * Get the list of hash upgrades
* @remarks * @remarks
* RAM cost: 0 GB * RAM cost: 0.5 GB
* *
* This function is only applicable for Hacknet Servers (the upgraded version of a Hacknet Node). * This function is only applicable for Hacknet Servers (the upgraded version of a Hacknet Node).
* *
@@ -3223,7 +3223,7 @@ export interface Hacknet {
/** /**
* Get the level of a hash upgrade. * Get the level of a hash upgrade.
* @remarks * @remarks
* RAM cost: 0 GB * RAM cost: 0.5 GB
* *
* This function is only applicable for Hacknet Servers (the upgraded version of a Hacknet Node). * This function is only applicable for Hacknet Servers (the upgraded version of a Hacknet Node).
* *
@@ -3234,7 +3234,7 @@ export interface Hacknet {
/** /**
* Get the multiplier to study. * Get the multiplier to study.
* @remarks * @remarks
* RAM cost: 0 GB * RAM cost: 0.5 GB
* *
* This function is only applicable for Hacknet Servers (the upgraded version of a Hacknet Node). * This function is only applicable for Hacknet Servers (the upgraded version of a Hacknet Node).
* *
@@ -3245,7 +3245,7 @@ export interface Hacknet {
/** /**
* Get the multiplier to training. * Get the multiplier to training.
* @remarks * @remarks
* RAM cost: 0 GB * RAM cost: 0.5 GB
* *
* This function is only applicable for Hacknet Servers (the upgraded version of a Hacknet Node). * This function is only applicable for Hacknet Servers (the upgraded version of a Hacknet Node).
* *
@@ -4879,7 +4879,7 @@ export interface Gang {
* *
* @returns Object containing territory and power information about all gangs, including the player's gang, if any. * @returns Object containing territory and power information about all gangs, including the player's gang, if any.
*/ */
getOtherGangInformation(): Record<string, GangOtherInfoObject>; getAllGangInformation(): Record<string, GangOtherInfoObject>;
/** /**
* Get information about a specific gang member. * Get information about a specific gang member.
@@ -6299,6 +6299,16 @@ interface HackingFormulas {
* @returns The calculated weaken time, in milliseconds. * @returns The calculated weaken time, in milliseconds.
*/ */
weakenTime(server: Server, player: Person): number; weakenTime(server: Server, player: Person): number;
/**
* Calculate the security decrease from a weaken operation.
* Unlike other hacking formulas, weaken effect depends only on thread count and
* core count, not on server or player properties. The core bonus formula is
* {@code 1 + (cores - 1) / 16}.
* @param threads - Number of threads running weaken.
* @param cores - Number of cores on the host server. Default 1.
* @returns The security decrease amount.
*/
weakenEffect(threads: number, cores?: number): number;
} }
/** /**
@@ -7014,113 +7024,93 @@ interface UserInterface {
export interface NS { export interface NS {
/** /**
* Namespace for {@link Hacknet | hacknet} functions. Some of this API contains spoilers. * Namespace for {@link Hacknet | hacknet} functions. Some of this API contains spoilers.
* @remarks RAM cost: 4 GB.
*/ */
readonly hacknet: Hacknet; readonly hacknet: Hacknet;
/** /**
* Namespace for {@link Bladeburner | Bladeburner} functions. Contains spoilers. * Namespace for {@link Bladeburner | Bladeburner} functions. Contains spoilers.
* @remarks RAM cost: 0 GB
*/ */
readonly bladeburner: Bladeburner; readonly bladeburner: Bladeburner;
/** /**
* Namespace for {@link CodingContract | coding contract} functions. * Namespace for {@link CodingContract | coding contract} functions.
* @remarks RAM cost: 0 GB
*/ */
readonly codingcontract: CodingContract; readonly codingcontract: CodingContract;
/** /**
* Namespace for {@link Cloud | cloud} functions. * Namespace for {@link Cloud | cloud} functions.
* @remarks RAM cost: 0 GB
*/ */
readonly cloud: Cloud; readonly cloud: Cloud;
/** /**
* Namespace for darknet functions. Contains spoilers. * Namespace for darknet functions. Contains spoilers.
* @remarks RAM cost: 0 GB
*/ */
readonly dnet: Darknet; readonly dnet: Darknet;
/** /**
* Namespace for {@link Format | formatting} functions. * Namespace for {@link Format | formatting} functions.
* @remarks RAM cost: 0 GB
*/ */
readonly format: Format; readonly format: Format;
/** /**
* Namespace for {@link Gang | gang} functions. Contains spoilers. * Namespace for {@link Gang | gang} functions. Contains spoilers.
* @remarks RAM cost: 0 GB
*/ */
readonly gang: Gang; readonly gang: Gang;
/** /**
* Namespace for {@link Go | Go} functions. * Namespace for {@link Go | Go} functions.
* @remarks RAM cost: 0 GB
*/ */
readonly go: Go; readonly go: Go;
/** /**
* Namespace for {@link Sleeve | sleeve} functions. Contains spoilers. * Namespace for {@link Sleeve | sleeve} functions. Contains spoilers.
* @remarks RAM cost: 0 GB
*/ */
readonly sleeve: Sleeve; readonly sleeve: Sleeve;
/** /**
* Namespace for {@link Stock | stock} functions. * Namespace for {@link Stock | stock} functions.
* @remarks RAM cost: 0 GB
*/ */
readonly stock: Stock; readonly stock: Stock;
/** /**
* Namespace for {@link Formulas | formulas} functions. * Namespace for {@link Formulas | formulas} functions.
* @remarks RAM cost: 0 GB
*/ */
readonly formulas: Formulas; readonly formulas: Formulas;
/** /**
* Namespace for {@link Stanek | Stanek} functions. Contains spoilers. * Namespace for {@link Stanek | Stanek} functions. Contains spoilers.
* @remarks RAM cost: 0 GB
*/ */
readonly stanek: Stanek; readonly stanek: Stanek;
/** /**
* Namespace for {@link Infiltration | infiltration} functions. * Namespace for {@link Infiltration | infiltration} functions.
* @remarks RAM cost: 0 GB
*/ */
readonly infiltration: Infiltration; readonly infiltration: Infiltration;
/** /**
* Namespace for {@link Corporation | corporation} functions. Contains spoilers. * Namespace for {@link Corporation | corporation} functions. Contains spoilers.
* @remarks RAM cost: 0 GB
*/ */
readonly corporation: Corporation; readonly corporation: Corporation;
/** /**
* Namespace for {@link UserInterface | user interface} functions. * Namespace for {@link UserInterface | user interface} functions.
* @remarks RAM cost: 0 GB
*/ */
readonly ui: UserInterface; readonly ui: UserInterface;
/** /**
* Namespace for {@link Singularity | singularity} functions. Contains spoilers. * Namespace for {@link Singularity | singularity} functions. Contains spoilers.
* @remarks RAM cost: 0 GB
*/ */
readonly singularity: Singularity; readonly singularity: Singularity;
/** /**
* Namespace for {@link Grafting | grafting} functions. Contains spoilers. * Namespace for {@link Grafting | grafting} functions. Contains spoilers.
* @remarks RAM cost: 0 GB
*/ */
readonly grafting: Grafting; readonly grafting: Grafting;
/** /**
* Arguments passed into the script. * Arguments passed into the script.
* *
* @remarks * These arguments can be accessed as a normal array by using the `[]` operator
* RAM cost: 0 GB
*
* Arguments passed into a script can be accessed as a normal array by using the `[]` operator
* (`args[0]`, `args[1]`, etc...). * (`args[0]`, `args[1]`, etc...).
* Arguments can be string, number, or boolean. * Arguments can be string, number, or boolean.
* Use `args.length` to get the number of arguments that were passed into a script. * Use `args.length` to get the number of arguments that were passed into a script.
@@ -7920,6 +7910,11 @@ export interface NS {
* // arguments to the script. * // arguments to the script.
* ns.exec("foo.js", "foodnstuff", 5, 1, "test"); * ns.exec("foo.js", "foodnstuff", 5, 1, "test");
* ``` * ```
*
* For darknet servers: A session must be established with the target server, and the script must be
* running on a server that is directly connected to the target, or the target must have a backdoor or
* stasis link installed.
*
* @param script - Filename of script to execute. This file must already exist on the target server. * @param script - Filename of script to execute. This file must already exist on the target server.
* @param host - Hostname/IP of the target server on which to execute the script. * @param host - Hostname/IP of the target server on which to execute the script.
* @param threadOrOptions - Either an integer number of threads for new script, or a {@link RunOptions} object. Threads defaults to 1. * @param threadOrOptions - Either an integer number of threads for new script, or a {@link RunOptions} object. Threads defaults to 1.
@@ -8057,7 +8052,11 @@ export interface NS {
* ns.scp(files, server, "home"); * ns.scp(files, server, "home");
* ``` * ```
* *
* For password-protected servers (such as darknet servers), a session must be established with the destination server before using this function. (The source server does not require a session.) * For darknet servers: The destination requires a session, but unlike {@link NS.exec | exec}, does not
* require a direct connection scp works at any distance. The source server has no darknet requirements
* (no session or connection needed). Use {@link Darknet.authenticate | dnet.authenticate} (requires direct
* connection) or {@link Darknet.connectToSession | dnet.connectToSession} (at any distance) to
* establish a session.
* *
* @param files - Filename or an array of filenames of script/literature files to copy. Note that if a file is located in a subdirectory, the filename must include the leading `/`. * @param files - Filename or an array of filenames of script/literature files to copy. Note that if a file is located in a subdirectory, the filename must include the leading `/`.
* @param destination - Hostname/IP of the destination server, which is the server to which the file will be copied. * @param destination - Hostname/IP of the destination server, which is the server to which the file will be copied.
-2
View File
@@ -18,7 +18,6 @@ import { OpenScript } from "./OpenScript";
import { Tab } from "./Tab"; import { Tab } from "./Tab";
import { SpecialServers } from "../../Server/data/SpecialServers"; import { SpecialServers } from "../../Server/data/SpecialServers";
const tabsMaxWidth = 1640;
const searchWidth = 180; const searchWidth = 180;
interface IProps { interface IProps {
@@ -101,7 +100,6 @@ export function Tabs({ scripts, currentScript, onTabClick, onTabClose, onTabUpda
<Droppable droppableId="tabs" direction="horizontal"> <Droppable droppableId="tabs" direction="horizontal">
{(provided, snapshot) => ( {(provided, snapshot) => (
<Box <Box
maxWidth={`${tabsMaxWidth}px`}
display="flex" display="flex"
flexGrow="1" flexGrow="1"
flexDirection="row" flexDirection="row"
+2 -1
View File
@@ -26,6 +26,7 @@ import { Settings } from "../Settings/Settings";
import type { ScriptKey } from "../utils/helpers/scriptKey"; import type { ScriptKey } from "../utils/helpers/scriptKey";
import { assertObject } from "../utils/TypeAssertion"; import { assertObject } from "../utils/TypeAssertion";
import { clampNumber } from "../utils/helpers/clampNumber"; import { clampNumber } from "../utils/helpers/clampNumber";
import { roundToTwo } from "../utils/helpers/roundToTwo";
export interface BaseServerConstructorParams { export interface BaseServerConstructorParams {
adminRights?: boolean; adminRights?: boolean;
@@ -233,7 +234,7 @@ export abstract class BaseServer implements IServer {
} }
updateRamUsed(ram: number): void { updateRamUsed(ram: number): void {
this.ramUsed = clampNumber(ram, 0, this.maxRam); this.ramUsed = roundToTwo(clampNumber(ram, 0, this.maxRam));
} }
pushProgram(program: ProgramFilePath | CompletedProgramName): void { pushProgram(program: ProgramFilePath | CompletedProgramName): void {
+4
View File
@@ -359,6 +359,7 @@ export function SidebarRoot(props: { page: Page }): React.ReactElement {
canStaneksGift && { key_: Page.StaneksGift, icon: DeveloperBoardIcon }, canStaneksGift && { key_: Page.StaneksGift, icon: DeveloperBoardIcon },
]} ]}
/> />
<Typography id="sidebar-extra-hook-0"></Typography>
<Divider /> <Divider />
<SidebarAccordion <SidebarAccordion
key_="Character" key_="Character"
@@ -386,6 +387,7 @@ export function SidebarRoot(props: { page: Page }): React.ReactElement {
canOpenGrafting && { key_: Page.Grafting, icon: BiotechIcon }, canOpenGrafting && { key_: Page.Grafting, icon: BiotechIcon },
]} ]}
/> />
<Typography id="sidebar-extra-hook-1"></Typography>
<Divider /> <Divider />
<SidebarAccordion <SidebarAccordion
key_="World" key_="World"
@@ -411,6 +413,7 @@ export function SidebarRoot(props: { page: Page }): React.ReactElement {
canDarkNet && { key_: Page.DarkNet, icon: ShareIcon }, canDarkNet && { key_: Page.DarkNet, icon: ShareIcon },
]} ]}
/> />
<Typography id="sidebar-extra-hook-2"></Typography>
<Divider /> <Divider />
<SidebarAccordion <SidebarAccordion
key_="Help" key_="Help"
@@ -428,6 +431,7 @@ export function SidebarRoot(props: { page: Page }): React.ReactElement {
process.env.NODE_ENV === "development" && { key_: Page.DevMenu, icon: DeveloperBoardIcon }, process.env.NODE_ENV === "development" && { key_: Page.DevMenu, icon: DeveloperBoardIcon },
]} ]}
/> />
<Typography id="sidebar-extra-hook-3"></Typography>
</List> </List>
</Drawer> </Drawer>
); );
+1 -1
View File
@@ -5,7 +5,7 @@ export const TerminalHelpText: string[] = [
" analyze Get information about the current machine ", " analyze Get information about the current machine ",
" backdoor Install a backdoor on the current machine ", " backdoor Install a backdoor on the current machine ",
" buy [-l/-a/program] Purchase a program through the Dark Web", " buy [-l/-a/program] Purchase a program through the Dark Web",
" cat [file] Display a .msg, .lit, or text file", " cat [file] Display the contents of a file",
" cd [dir] Change to a new directory", " cd [dir] Change to a new directory",
" changelog Display changelog", " changelog Display changelog",
" check [script] [args...] Print a script's logs to Terminal", " check [script] [args...] Print a script's logs to Terminal",
+7 -3
View File
@@ -78,6 +78,7 @@ import { commitHash } from "../utils/helpers/commitHash";
import { apr1 } from "./commands/apr1"; import { apr1 } from "./commands/apr1";
import { changelog } from "./commands/changelog"; import { changelog } from "./commands/changelog";
import { clear } from "./commands/clear"; import { clear } from "./commands/clear";
import { mkdir } from "./commands/mkdir";
import { currentNodeMults } from "../BitNode/BitNodeMultipliers"; import { currentNodeMults } from "../BitNode/BitNodeMultipliers";
import { Engine } from "../engine"; import { Engine } from "../engine";
import { Directory, resolveDirectory, root } from "../Paths/Directory"; import { Directory, resolveDirectory, root } from "../Paths/Directory";
@@ -134,8 +135,12 @@ export const TerminalCommands: Record<string, (args: (string | number | boolean)
vim: vim, vim: vim,
weaken: weaken, weaken: weaken,
wget: wget, wget: wget,
mkdir: mkdir,
}; };
// "mkdir" is a "hidden" command; i.e., it is not shown in help text or autocomplete.
export const supportedCommands = Object.keys(TerminalCommands).filter((command) => command !== "mkdir");
export class Terminal { export class Terminal {
// Flags to determine whether the player is currently running a hack or an analyze // Flags to determine whether the player is currently running a hack or an analyze
action: TTimer | null = null; action: TTimer | null = null;
@@ -877,8 +882,7 @@ export class Terminal {
} }
function findSimilarCommands(command: string): string[] { function findSimilarCommands(command: string): string[] {
const commands = Object.keys(TerminalCommands); const offByOneLetter = supportedCommands.filter((c) => {
const offByOneLetter = commands.filter((c) => {
if (c.length !== command.length) return false; if (c.length !== command.length) return false;
let diff = 0; let diff = 0;
for (let i = 0; i < c.length; i++) { for (let i = 0; i < c.length; i++) {
@@ -886,6 +890,6 @@ function findSimilarCommands(command: string): string[] {
} }
return diff === 1; return diff === 1;
}); });
const subset = commands.filter((c) => c.includes(command)).sort((a, b) => a.length - b.length); const subset = supportedCommands.filter((c) => c.includes(command)).sort((a, b) => a.length - b.length);
return Array.from(new Set([...offByOneLetter, ...subset])).slice(0, 3); return Array.from(new Set([...offByOneLetter, ...subset])).slice(0, 3);
} }
+8
View File
@@ -0,0 +1,8 @@
import { Terminal } from "../../Terminal";
export function mkdir(): void {
Terminal.error(
"Directories do not exist in the Bitburner filesystem. They are simply part of the file path.\n" +
`For example, with "/foo/bar.txt", there is no actual "/foo" directory.`,
);
}
+11 -4
View File
@@ -11,20 +11,27 @@ import libarg from "arg";
import { getAllDirectories, resolveDirectory, root } from "../Paths/Directory"; import { getAllDirectories, resolveDirectory, root } from "../Paths/Directory";
import { isLegacyScript, resolveScriptFilePath } from "../Paths/ScriptFilePath"; import { isLegacyScript, resolveScriptFilePath } from "../Paths/ScriptFilePath";
import { enums } from "../NetscriptFunctions"; import { enums } from "../NetscriptFunctions";
import { TerminalCommands } from "./Terminal"; import { supportedCommands } from "./Terminal";
import { Terminal } from "../Terminal"; import { Terminal } from "../Terminal";
import { parseUnknownError } from "../utils/ErrorHelper"; import { parseUnknownError } from "../utils/ErrorHelper";
import { DarknetServer } from "../Server/DarknetServer"; import { DarknetServer } from "../Server/DarknetServer";
import { CompletedProgramName } from "@enums"; import { CompletedProgramName } from "@enums";
/** Extract the text being autocompleted, handling unclosed double quotes as a single token */
export function extractCurrentText(terminalText: string): string {
const quoteCount = (terminalText.match(/"/g) || []).length;
if (quoteCount % 2 === 1) return terminalText.substring(terminalText.lastIndexOf('"'));
return /[^ ]*$/.exec(terminalText)?.[0] ?? "";
}
/** Suggest all completion possibilities for the last argument in the last command being typed /** Suggest all completion possibilities for the last argument in the last command being typed
* @param terminalText The current full text entered in the terminal * @param terminalText The current full text entered in the terminal
* @param baseDir The current working directory. * @param baseDir The current working directory.
* @returns Array of possible string replacements for the current text being autocompleted. * @returns Array of possible string replacements for the current text being autocompleted.
*/ */
export async function getTabCompletionPossibilities(terminalText: string, baseDir = root): Promise<string[]> { export async function getTabCompletionPossibilities(terminalText: string, baseDir = root): Promise<string[]> {
// Get the current command text // Get the current command text, treating unclosed quotes as a single token
const currentText = /[^ ]*$/.exec(terminalText)?.[0] ?? ""; const currentText = extractCurrentText(terminalText);
// Remove the current text from the commands string // Remove the current text from the commands string
const valueWithoutCurrent = terminalText.substring(0, terminalText.length - currentText.length); const valueWithoutCurrent = terminalText.substring(0, terminalText.length - currentText.length);
// Parse the commands string, this handles alias replacement as well. // Parse the commands string, this handles alias replacement as well.
@@ -84,7 +91,7 @@ export async function getTabCompletionPossibilities(terminalText: string, baseDi
const addAliases = () => addGeneric({ iterable: Aliases.keys() }); const addAliases = () => addGeneric({ iterable: Aliases.keys() });
const addGlobalAliases = () => addGeneric({ iterable: GlobalAliases.keys() }); const addGlobalAliases = () => addGeneric({ iterable: GlobalAliases.keys() });
const addCommands = () => addGeneric({ iterable: Object.keys(TerminalCommands) }); const addCommands = () => addGeneric({ iterable: supportedCommands });
const addDarkwebItems = () => addGeneric({ iterable: Object.values(DarkWebItems).map((item) => item.program) }); const addDarkwebItems = () => addGeneric({ iterable: Object.values(DarkWebItems).map((item) => item.program) });
const addServerNames = () => const addServerNames = () =>
addGeneric({ addGeneric({
+6 -3
View File
@@ -6,7 +6,7 @@ import { Paper, Popper, TextField, Typography } from "@mui/material";
import { KEY } from "../../utils/KeyboardEventKey"; import { KEY } from "../../utils/KeyboardEventKey";
import { Terminal } from "../../Terminal"; import { Terminal } from "../../Terminal";
import { Player } from "@player"; import { Player } from "@player";
import { getTabCompletionPossibilities } from "../getTabCompletionPossibilities"; import { extractCurrentText, getTabCompletionPossibilities } from "../getTabCompletionPossibilities";
import { Settings } from "../../Settings/Settings"; import { Settings } from "../../Settings/Settings";
import { longestCommonStart } from "../../utils/StringHelperFunctions"; import { longestCommonStart } from "../../utils/StringHelperFunctions";
import { exceptionAlert } from "../../utils/helpers/exceptionAlert"; import { exceptionAlert } from "../../utils/helpers/exceptionAlert";
@@ -266,13 +266,16 @@ export function TerminalInput(): React.ReactElement {
if (possibilities.length === 0) return; if (possibilities.length === 0) return;
setSearchResults([]); setSearchResults([]);
// Use quote-aware replacement: if mid-quote, replace from the opening quote
const currentText = extractCurrentText(value);
const replacePattern = currentText.startsWith('"') ? /"[^"]*$/ : /[^ ]*$/;
if (possibilities.length === 1) { if (possibilities.length === 1) {
saveValue(value.replace(/[^ ]*$/, possibilities[0]) + " "); saveValue(value.replace(replacePattern, possibilities[0]) + " ");
return; return;
} }
// More than one possibility, check to see if there is a longer common string than currentText. // More than one possibility, check to see if there is a longer common string than currentText.
const longestMatch = longestCommonStart(possibilities); const longestMatch = longestCommonStart(possibilities);
saveValue(value.replace(/[^ ]*$/, longestMatch)); saveValue(value.replace(replacePattern, longestMatch));
setPossibilities(possibilities); setPossibilities(possibilities);
} }
+1 -1
View File
@@ -130,7 +130,7 @@ export const calculateCompanyWorkStats = (
// If player has SF-11, calculate salary multiplier from favor // If player has SF-11, calculate salary multiplier from favor
const favorMult = isNaN(favor) ? 1 : 1 + favor / 100; const favorMult = isNaN(favor) ? 1 : 1 + favor / 100;
const bn11Mult = Player.activeSourceFileLvl(11) > 0 ? favorMult : 1; const bn11Mult = Player.activeSourceFileLvl(11) > 0 ? favorMult : 1;
const sf15Mult = Player.sourceFileLvl(15) > 1 ? getMultiplierFromCharisma(1.5) : 1; const sf15Mult = Player.activeSourceFileLvl(15) > 1 ? getMultiplierFromCharisma(1.5) : 1;
const gains = scaleWorkStats( const gains = scaleWorkStats(
multWorkStats( multWorkStats(
+2 -2
View File
@@ -157,7 +157,7 @@ const Engine = {
messages: 150, messages: 150,
mechanicProcess: 5, // Process Bladeburner mechanicProcess: 5, // Process Bladeburner
contractGeneration: 3000, // Generate Coding Contracts contractGeneration: 3000, // Generate Coding Contracts
achievementsCounter: 60, // Check if we have new achievements achievementsCounter: 5, // Check if we have new achievements
}, },
decrementAllCounters: function (numCycles = 1) { decrementAllCounters: function (numCycles = 1) {
@@ -215,7 +215,7 @@ const Engine = {
if (Engine.Counters.achievementsCounter <= 0) { if (Engine.Counters.achievementsCounter <= 0) {
calculateAchievements(); calculateAchievements();
Engine.Counters.achievementsCounter = 300; Engine.Counters.achievementsCounter = 5;
} }
// This **MUST** remain the last block in the function! // This **MUST** remain the last block in the function!
+2 -3
View File
@@ -17,7 +17,7 @@ import { StatsRow } from "./React/StatsRow";
import { StatsTable } from "./React/StatsTable"; import { StatsTable } from "./React/StatsTable";
import { useCycleRerender } from "./React/hooks"; import { useCycleRerender } from "./React/hooks";
import { getMaxRep } from "../Go/effects/effect"; import { getMaxRep } from "../Go/effects/effect";
import { canAccessBitNodeFeature, knowAboutBitverse } from "../BitNode/BitNodeUtils"; import { canAccessBitNodeFeature, getBitNodeLevel, knowAboutBitverse } from "../BitNode/BitNodeUtils";
interface EmployersModalProps { interface EmployersModalProps {
open: boolean; open: boolean;
@@ -103,11 +103,10 @@ function MultiplierTable(props: MultTableProps): React.ReactElement {
function CurrentBitNode(): React.ReactElement { function CurrentBitNode(): React.ReactElement {
if (knowAboutBitverse()) { if (knowAboutBitverse()) {
const index = "BitNode" + Player.bitNodeN; const index = "BitNode" + Player.bitNodeN;
const lvl = Math.min(Player.sourceFileLvl(Player.bitNodeN) + 1, Player.bitNodeN === 12 ? Number.MAX_VALUE : 3);
return ( return (
<Paper sx={{ mb: 1, p: 1 }}> <Paper sx={{ mb: 1, p: 1 }}>
<Typography variant="h5"> <Typography variant="h5">
BitNode {Player.bitNodeN}: {BitNodes[index].name} (Level {lvl}) BitNode {Player.bitNodeN}: {BitNodes[index].name} (Level {getBitNodeLevel()})
</Typography> </Typography>
<Typography component="div" sx={{ whiteSpace: "pre-wrap", overflowWrap: "break-word" }}> <Typography component="div" sx={{ whiteSpace: "pre-wrap", overflowWrap: "break-word" }}>
{BitNodes[index].info} {BitNodes[index].info}
+12 -2
View File
@@ -193,8 +193,18 @@ export const formatInt = (n: number) => formatNumber(n, 3, 1000, true);
export const formatSleeveMemory = formatInt; export const formatSleeveMemory = formatInt;
export const formatShares = formatInt; export const formatShares = formatInt;
/** Display an integer up to 999,999 before collapsing to suffixed form with 3 fractional digits */ /**
export const formatHp = (n: number) => formatNumber(n, 3, 1e6, true); * Format a number using basicFormatter for values below 1e6, and a suffixed form with up to 3 fractional digits for
* values at or above 1e6. This uses formatNumber, so check that function for nuanced details.
*
* Values in the range (0, 0.001) are displayed in exponential notation.
*/
export const formatHp = (n: number) => {
if (n > 0 && n < 0.001) {
return formatExponential(n);
}
return formatNumber(n, 3, 1e6, true);
};
export const formatThreads = formatHp; export const formatThreads = formatHp;
/** Display an integer up to 999,999,999 before collapsing to suffixed form with 3 fractional digits */ /** Display an integer up to 999,999,999 before collapsing to suffixed form with 3 fractional digits */
+15
View File
@@ -624,5 +624,20 @@ export const breakingChanges300: VersionBreakingChange = {
`- Read the "General rules", "String conversion", and "Tips" sections on the "Coding Contracts" page carefully.`, `- Read the "General rules", "String conversion", and "Tips" sections on the "Coding Contracts" page carefully.`,
showWarning: false, showWarning: false,
}, },
{
brokenAPIs: [
{
name: "ns.gang.getOtherGangInformation",
migration: {
searchValue: "getOtherGangInformation",
replaceValue: "getAllGangInformation",
},
},
],
info:
"ns.gang.getOtherGangInformation() was renamed to ns.gang.getAllGangInformation().\n" +
"The function was renamed because it returns information about all gangs, including the player's own gang.",
showWarning: false,
},
], ],
}; };

Some files were not shown because too many files have changed in this diff Show More