mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2026-04-16 06:18:42 +02:00
Compare commits
41 Commits
7425d8a8fd
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
45bce6e45e | ||
|
|
c21d1f44b2 | ||
|
|
956e00f789 | ||
|
|
c5536d252b | ||
|
|
a99ca64455 | ||
|
|
cb14655325 | ||
|
|
9ab3e0bcb4 | ||
|
|
cc9144c01b | ||
|
|
fb3fa00b3d | ||
|
|
8cbd6ff9e1 | ||
|
|
00a1bc2f6e | ||
|
|
be6fcd206f | ||
|
|
a6a112198e | ||
|
|
732aadb2d6 | ||
|
|
85c9ac0181 | ||
|
|
e232f37550 | ||
|
|
6074721c59 | ||
|
|
09e46d757b | ||
|
|
5cb0d559df | ||
|
|
54287e5f7f | ||
|
|
d25b1676ab | ||
|
|
d6299becd6 | ||
|
|
19b137e2fb | ||
|
|
ee2949418f | ||
|
|
fbd7930ab2 | ||
|
|
8b3c7c13c5 | ||
|
|
996bb01075 | ||
|
|
eb4e193fac | ||
|
|
0c39fc3720 | ||
|
|
44741a7795 | ||
|
|
2818969c8a | ||
|
|
de9311f820 | ||
|
|
15d463d583 | ||
|
|
2819947378 | ||
|
|
48fad72b6a | ||
|
|
63aa4d2a45 | ||
|
|
abdf3082ca | ||
|
|
8dcccdc5bb | ||
|
|
d1b6acc57a | ||
|
|
dc4ea8452c | ||
|
|
5fc54809de |
16
.github/workflows/build-artifacts.yml
vendored
16
.github/workflows/build-artifacts.yml
vendored
@@ -2,7 +2,17 @@ name: Build artifacts
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
git-sha:
|
||||
description: "Commit SHA-1 to checkout"
|
||||
required: false
|
||||
default: ""
|
||||
workflow_call:
|
||||
inputs:
|
||||
git-sha:
|
||||
type: string
|
||||
required: false
|
||||
default: ""
|
||||
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
@@ -13,6 +23,8 @@ jobs:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.inputs.git-sha || inputs.git-sha || github.sha }}
|
||||
- name: Use Node.js 24
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
@@ -46,6 +58,8 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.inputs.git-sha || inputs.git-sha || github.sha }}
|
||||
- name: Use Node.js 24
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
@@ -77,6 +91,8 @@ jobs:
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.inputs.git-sha || inputs.git-sha || github.sha }}
|
||||
- name: Use Node.js 24
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
|
||||
@@ -25,5 +25,77 @@
|
||||
<div>
|
||||
<h1>Close me when operation is completed.</h1>
|
||||
</div>
|
||||
<!-- Use esm for top-level await -->
|
||||
<script type="module">
|
||||
const databaseName = "bitburnerSave";
|
||||
// Check src/db.ts to see why the current max version is 2. If the database version is greater than this value, it
|
||||
// means that the code in this file is outdated.
|
||||
const maxDatabaseVersion = 2;
|
||||
const databases = await window.indexedDB.databases();
|
||||
const database = databases.find((info) => info.name === databaseName);
|
||||
if (!database) {
|
||||
alert("There is no save data");
|
||||
// This is the simplest way to stop execution in top-level code without using a labeled block or IIFE.
|
||||
throw new Error("There is no save data");
|
||||
}
|
||||
if (database.version === undefined || database.version > maxDatabaseVersion) {
|
||||
alert(`Invalid database version: ${database.version}`);
|
||||
throw new Error(`Invalid database version: ${database.version}`);
|
||||
}
|
||||
// Do NOT specify the version. We must open the database at the current version; otherwise, we will trigger
|
||||
// onupgradeneeded.
|
||||
const dbRequest = window.indexedDB.open(databaseName);
|
||||
dbRequest.onerror = (event) => {
|
||||
console.error(event.target.error);
|
||||
alert(event.target.error);
|
||||
};
|
||||
dbRequest.onsuccess = () => {
|
||||
const db = dbRequest.result;
|
||||
try {
|
||||
if (!db.objectStoreNames.contains("savestring")) {
|
||||
alert("There is no save data");
|
||||
return;
|
||||
}
|
||||
const transaction = db.transaction(["savestring"], "readonly");
|
||||
const objectStore = transaction.objectStore("savestring");
|
||||
const request = objectStore.get("save");
|
||||
request.onsuccess = () => {
|
||||
if (request.result == null) {
|
||||
alert("There is no save data");
|
||||
return;
|
||||
}
|
||||
let isBinaryFormat;
|
||||
if (request.result instanceof Uint8Array) {
|
||||
// All modules in the Electron folder are CommonJS, so importing them here would be really difficult. The
|
||||
// isBinaryFormat function is very small, so let's inline it here.
|
||||
isBinaryFormat = true;
|
||||
const magicBytesOfDeflateGzip = [0x1f, 0x8b, 0x08];
|
||||
for (let i = 0; i < magicBytesOfDeflateGzip.length; ++i) {
|
||||
if (magicBytesOfDeflateGzip[i] !== request.result[i]) {
|
||||
isBinaryFormat = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
isBinaryFormat = false;
|
||||
}
|
||||
const extension = isBinaryFormat ? "json.gz" : "json";
|
||||
const filename = `bitburnerSave_${Date.now()}.${extension}`;
|
||||
const blob = new Blob([request.result]);
|
||||
const anchorElement = document.createElement("a");
|
||||
const url = URL.createObjectURL(blob);
|
||||
anchorElement.href = url;
|
||||
anchorElement.download = filename;
|
||||
anchorElement.click();
|
||||
setTimeout(function () {
|
||||
URL.revokeObjectURL(url);
|
||||
}, 0);
|
||||
};
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
alert(error);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -258,7 +258,6 @@ app.on("ready", async () => {
|
||||
await window.loadFile("export.html");
|
||||
window.show();
|
||||
setStopProcessHandler(window);
|
||||
await utils.exportSave(window);
|
||||
} else {
|
||||
window = await startWindow(process.argv.includes("--no-scripts"));
|
||||
if (global.steamworksError) {
|
||||
|
||||
@@ -90,36 +90,6 @@ function showErrorBox(title, error) {
|
||||
dialog.showErrorBox(title, `${error.name}\n\n${error.message}`);
|
||||
}
|
||||
|
||||
function exportSaveFromIndexedDb() {
|
||||
return new Promise((resolve) => {
|
||||
const dbRequest = indexedDB.open("bitburnerSave");
|
||||
dbRequest.onsuccess = () => {
|
||||
const db = dbRequest.result;
|
||||
const transaction = db.transaction(["savestring"], "readonly");
|
||||
const store = transaction.objectStore("savestring");
|
||||
const request = store.get("save");
|
||||
request.onsuccess = () => {
|
||||
const file = new Blob([request.result], { type: "text/plain" });
|
||||
const a = document.createElement("a");
|
||||
const url = URL.createObjectURL(file);
|
||||
a.href = url;
|
||||
a.download = "save.json";
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
setTimeout(function () {
|
||||
document.body.removeChild(a);
|
||||
window.URL.revokeObjectURL(url);
|
||||
resolve();
|
||||
}, 0);
|
||||
};
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
async function exportSave(window) {
|
||||
await window.webContents.executeJavaScript(`${exportSaveFromIndexedDb.toString()}; exportSaveFromIndexedDb();`, true);
|
||||
}
|
||||
|
||||
async function writeTerminal(window, message, type = null) {
|
||||
await window.webContents.executeJavaScript(`window.appNotifier.terminal("${message}", "${type}");`, true);
|
||||
}
|
||||
@@ -186,7 +156,6 @@ function initializeLogLevelConfig() {
|
||||
module.exports = {
|
||||
reloadAndKill,
|
||||
showErrorBox,
|
||||
exportSave,
|
||||
attachUnresponsiveAppHandler,
|
||||
detachUnresponsiveAppHandler,
|
||||
writeTerminal,
|
||||
|
||||
19
markdown/bitburner.activefragment.chargedeffect.md
Normal file
19
markdown/bitburner.activefragment.chargedeffect.md
Normal file
@@ -0,0 +1,19 @@
|
||||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [bitburner](./bitburner.md) > [ActiveFragment](./bitburner.activefragment.md) > [chargedEffect](./bitburner.activefragment.chargedeffect.md)
|
||||
|
||||
## ActiveFragment.chargedEffect property
|
||||
|
||||
This is the raw value of the modifier used to calculate the effect on your multipliers. It may not be a multiplier.
|
||||
|
||||
With fragments that increase a multiplier, this value is a multiplier. For example, with "+x% hacknet production" fragment, a value of 1.25 will multiply the "hacknet\_node\_money" multiplier by 1.25. The UI will show "+25% hacknet production".
|
||||
|
||||
With fragments that decrease a multiplier, you need to invert this value. For example, with "-x% cheaper hacknet costs" fragment, a value of 1.25 means the "hacknet\_node\_purchase\_cost" (and other relevant cost multipliers) will be multiplied by 0.8 (1 / 1.25). The UI will show "20% cheaper hacknet costs".
|
||||
|
||||
With booster fragments, this value is always 1. Booster fragments only boost non-booster fragments. They don't directly boost your multipliers.
|
||||
|
||||
**Signature:**
|
||||
|
||||
```typescript
|
||||
chargedEffect: number;
|
||||
```
|
||||
@@ -37,6 +37,31 @@ Description
|
||||
</th></tr></thead>
|
||||
<tbody><tr><td>
|
||||
|
||||
[chargedEffect](./bitburner.activefragment.chargedeffect.md)
|
||||
|
||||
|
||||
</td><td>
|
||||
|
||||
|
||||
</td><td>
|
||||
|
||||
number
|
||||
|
||||
|
||||
</td><td>
|
||||
|
||||
This is the raw value of the modifier used to calculate the effect on your multipliers. It may not be a multiplier.
|
||||
|
||||
With fragments that increase a multiplier, this value is a multiplier. For example, with "+x% hacknet production" fragment, a value of 1.25 will multiply the "hacknet\_node\_money" multiplier by 1.25. The UI will show "+25% hacknet production".
|
||||
|
||||
With fragments that decrease a multiplier, you need to invert this value. For example, with "-x% cheaper hacknet costs" fragment, a value of 1.25 means the "hacknet\_node\_purchase\_cost" (and other relevant cost multipliers) will be multiplied by 0.8 (1 / 1.25). The UI will show "20% cheaper hacknet costs".
|
||||
|
||||
With booster fragments, this value is always 1. Booster fragments only boost non-booster fragments. They don't directly boost your multipliers.
|
||||
|
||||
|
||||
</td></tr>
|
||||
<tr><td>
|
||||
|
||||
[highestCharge](./bitburner.activefragment.highestcharge.md)
|
||||
|
||||
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [bitburner](./bitburner.md) > [Gang](./bitburner.gang.md) > [getOtherGangInformation](./bitburner.gang.getotherganginformation.md)
|
||||
[Home](./index.md) > [bitburner](./bitburner.md) > [Gang](./bitburner.gang.md) > [getAllGangInformation](./bitburner.gang.getallganginformation.md)
|
||||
|
||||
## Gang.getOtherGangInformation() method
|
||||
## Gang.getAllGangInformation() method
|
||||
|
||||
Get information about all gangs.
|
||||
|
||||
**Signature:**
|
||||
|
||||
```typescript
|
||||
getOtherGangInformation(): Record<string, GangOtherInfoObject>;
|
||||
getAllGangInformation(): Record<string, GangOtherInfoObject>;
|
||||
```
|
||||
**Returns:**
|
||||
|
||||
@@ -61,6 +61,17 @@ Check if you can recruit a new gang member.
|
||||
Create a gang.
|
||||
|
||||
|
||||
</td></tr>
|
||||
<tr><td>
|
||||
|
||||
[getAllGangInformation()](./bitburner.gang.getallganginformation.md)
|
||||
|
||||
|
||||
</td><td>
|
||||
|
||||
Get information about all gangs.
|
||||
|
||||
|
||||
</td></tr>
|
||||
<tr><td>
|
||||
|
||||
@@ -182,17 +193,6 @@ Get information about a specific gang member.
|
||||
List all gang members.
|
||||
|
||||
|
||||
</td></tr>
|
||||
<tr><td>
|
||||
|
||||
[getOtherGangInformation()](./bitburner.gang.getotherganginformation.md)
|
||||
|
||||
|
||||
</td><td>
|
||||
|
||||
Get information about all gangs.
|
||||
|
||||
|
||||
</td></tr>
|
||||
<tr><td>
|
||||
|
||||
|
||||
@@ -126,6 +126,17 @@ Calculate hack percent for one thread. (Ex: 0.25 would steal 25% of the server's
|
||||
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>
|
||||
<tr><td>
|
||||
|
||||
|
||||
72
markdown/bitburner.hackingformulas.weakeneffect.md
Normal file
72
markdown/bitburner.hackingformulas.weakeneffect.md
Normal file
@@ -0,0 +1,72 @@
|
||||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [bitburner](./bitburner.md) > [HackingFormulas](./bitburner.hackingformulas.md) > [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
|
||||
|
||||
RAM cost: 0 GB
|
||||
RAM cost: 0.5 GB
|
||||
|
||||
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
|
||||
|
||||
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.
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ Level of the upgrade.
|
||||
|
||||
## 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).
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ An array containing the available upgrades
|
||||
|
||||
## 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).
|
||||
|
||||
|
||||
@@ -72,7 +72,7 @@ Cost of upgrading the specified Hacknet Node.
|
||||
|
||||
## Remarks
|
||||
|
||||
RAM cost: 0 GB
|
||||
RAM cost: 0.5 GB
|
||||
|
||||
Returns the cost of upgrading the specified Hacknet Node by n levels.
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ Object containing a variety of stats about the specified Hacknet Node.
|
||||
|
||||
## Remarks
|
||||
|
||||
RAM cost: 0 GB
|
||||
RAM cost: 0.5 GB
|
||||
|
||||
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
|
||||
|
||||
RAM cost: 0 GB
|
||||
RAM cost: 0.5 GB
|
||||
|
||||
Returns the cost of purchasing a new Hacknet Node.
|
||||
|
||||
|
||||
@@ -72,7 +72,7 @@ Cost of upgrading the specified Hacknet Node's RAM.
|
||||
|
||||
## Remarks
|
||||
|
||||
RAM cost: 0 GB
|
||||
RAM cost: 0.5 GB
|
||||
|
||||
Returns the cost of upgrading the RAM of the specified Hacknet Node n times.
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ Multiplier.
|
||||
|
||||
## 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).
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ Multiplier.
|
||||
|
||||
## 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).
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ Number of hashes you can store.
|
||||
|
||||
## 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).
|
||||
|
||||
|
||||
@@ -72,7 +72,7 @@ Number of hashes required for the specified upgrade.
|
||||
|
||||
## 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).
|
||||
|
||||
|
||||
@@ -19,5 +19,5 @@ Maximum number of hacknet nodes.
|
||||
|
||||
## Remarks
|
||||
|
||||
RAM cost: 0 GB
|
||||
RAM cost: 0.5 GB
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ Number of hashes you have.
|
||||
|
||||
## 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).
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ Number of hacknet nodes.
|
||||
|
||||
## Remarks
|
||||
|
||||
RAM cost: 0 GB
|
||||
RAM cost: 0.5 GB
|
||||
|
||||
Returns the number of Hacknet Nodes you own.
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ The index of the Hacknet Node or if the player cannot afford to purchase a new H
|
||||
|
||||
## 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 Node’s name (e.g. The Hacknet Node named `hacknet-node-4` will have an index of 4).
|
||||
|
||||
|
||||
@@ -88,7 +88,7 @@ True if the upgrade is successfully purchased, and false otherwise.
|
||||
|
||||
## 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).
|
||||
|
||||
|
||||
@@ -72,7 +72,7 @@ True if the Hacknet Node’s cache level is successfully upgraded, false otherwi
|
||||
|
||||
## 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).
|
||||
|
||||
|
||||
@@ -72,7 +72,7 @@ True if the Hacknet Node’s cores are successfully purchased, false otherwise.
|
||||
|
||||
## Remarks
|
||||
|
||||
RAM cost: 0 GB
|
||||
RAM cost: 0.5 GB
|
||||
|
||||
Tries to purchase n cores for the specified Hacknet Node.
|
||||
|
||||
|
||||
@@ -72,7 +72,7 @@ True if the Hacknet Node’s level is successfully upgraded, false otherwise.
|
||||
|
||||
## Remarks
|
||||
|
||||
RAM cost: 0 GB
|
||||
RAM cost: 0.5 GB
|
||||
|
||||
Tries to upgrade the level of the specified Hacknet Node by n.
|
||||
|
||||
|
||||
@@ -72,7 +72,7 @@ True if the Hacknet Node’s RAM is successfully upgraded, false otherwise.
|
||||
|
||||
## Remarks
|
||||
|
||||
RAM cost: 0 GB
|
||||
RAM cost: 0.5 GB
|
||||
|
||||
Tries to upgrade the specified Hacknet Node’s RAM n times. Note that each upgrade doubles the Node’s RAM. So this is equivalent to multiplying the Node’s RAM by 2 n.
|
||||
|
||||
|
||||
@@ -6,18 +6,14 @@
|
||||
|
||||
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:**
|
||||
|
||||
```typescript
|
||||
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
|
||||
|
||||
`run example.js 7 text true`
|
||||
|
||||
@@ -11,8 +11,3 @@ Namespace for [Bladeburner](./bitburner.bladeburner.md) functions. Contains spoi
|
||||
```typescript
|
||||
readonly bladeburner: Bladeburner;
|
||||
```
|
||||
|
||||
## Remarks
|
||||
|
||||
RAM cost: 0 GB
|
||||
|
||||
|
||||
@@ -11,8 +11,3 @@ Namespace for [cloud](./bitburner.cloud.md) functions.
|
||||
```typescript
|
||||
readonly cloud: Cloud;
|
||||
```
|
||||
|
||||
## Remarks
|
||||
|
||||
RAM cost: 0 GB
|
||||
|
||||
|
||||
@@ -11,8 +11,3 @@ Namespace for [coding contract](./bitburner.codingcontract.md) functions.
|
||||
```typescript
|
||||
readonly codingcontract: CodingContract;
|
||||
```
|
||||
|
||||
## Remarks
|
||||
|
||||
RAM cost: 0 GB
|
||||
|
||||
|
||||
@@ -11,8 +11,3 @@ Namespace for [corporation](./bitburner.corporation.md) functions. Contains spoi
|
||||
```typescript
|
||||
readonly corporation: Corporation;
|
||||
```
|
||||
|
||||
## Remarks
|
||||
|
||||
RAM cost: 0 GB
|
||||
|
||||
|
||||
@@ -11,8 +11,3 @@ Namespace for darknet functions. Contains spoilers.
|
||||
```typescript
|
||||
readonly dnet: Darknet;
|
||||
```
|
||||
|
||||
## Remarks
|
||||
|
||||
RAM cost: 0 GB
|
||||
|
||||
|
||||
@@ -132,4 +132,5 @@ ns.exec("generic-hack.js", "joesguns", {threads: 10});
|
||||
// arguments to the script.
|
||||
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.
|
||||
|
||||
|
||||
@@ -11,8 +11,3 @@ Namespace for [formatting](./bitburner.format.md) functions.
|
||||
```typescript
|
||||
readonly format: Format;
|
||||
```
|
||||
|
||||
## Remarks
|
||||
|
||||
RAM cost: 0 GB
|
||||
|
||||
|
||||
@@ -11,8 +11,3 @@ Namespace for [formulas](./bitburner.formulas.md) functions.
|
||||
```typescript
|
||||
readonly formulas: Formulas;
|
||||
```
|
||||
|
||||
## Remarks
|
||||
|
||||
RAM cost: 0 GB
|
||||
|
||||
|
||||
@@ -11,8 +11,3 @@ Namespace for [gang](./bitburner.gang.md) functions. Contains spoilers.
|
||||
```typescript
|
||||
readonly gang: Gang;
|
||||
```
|
||||
|
||||
## Remarks
|
||||
|
||||
RAM cost: 0 GB
|
||||
|
||||
|
||||
@@ -11,8 +11,3 @@ Namespace for [Go](./bitburner.go.md) functions.
|
||||
```typescript
|
||||
readonly go: Go;
|
||||
```
|
||||
|
||||
## Remarks
|
||||
|
||||
RAM cost: 0 GB
|
||||
|
||||
|
||||
@@ -11,8 +11,3 @@ Namespace for [grafting](./bitburner.grafting.md) functions. Contains spoilers.
|
||||
```typescript
|
||||
readonly grafting: Grafting;
|
||||
```
|
||||
|
||||
## Remarks
|
||||
|
||||
RAM cost: 0 GB
|
||||
|
||||
|
||||
@@ -11,8 +11,3 @@ Namespace for [hacknet](./bitburner.hacknet.md) functions. Some of this API cont
|
||||
```typescript
|
||||
readonly hacknet: Hacknet;
|
||||
```
|
||||
|
||||
## Remarks
|
||||
|
||||
RAM cost: 4 GB.
|
||||
|
||||
|
||||
@@ -11,8 +11,3 @@ Namespace for [infiltration](./bitburner.infiltration.md) functions.
|
||||
```typescript
|
||||
readonly infiltration: Infiltration;
|
||||
```
|
||||
|
||||
## Remarks
|
||||
|
||||
RAM cost: 0 GB
|
||||
|
||||
|
||||
@@ -69,6 +69,8 @@ Description
|
||||
|
||||
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>
|
||||
<tr><td>
|
||||
|
||||
@@ -112,5 +112,5 @@ const server = ns.args[0];
|
||||
const files = ["hack.js", "weaken.js", "grow.js"];
|
||||
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.
|
||||
|
||||
|
||||
@@ -11,8 +11,3 @@ Namespace for [singularity](./bitburner.singularity.md) functions. Contains spoi
|
||||
```typescript
|
||||
readonly singularity: Singularity;
|
||||
```
|
||||
|
||||
## Remarks
|
||||
|
||||
RAM cost: 0 GB
|
||||
|
||||
|
||||
@@ -11,8 +11,3 @@ Namespace for [sleeve](./bitburner.sleeve.md) functions. Contains spoilers.
|
||||
```typescript
|
||||
readonly sleeve: Sleeve;
|
||||
```
|
||||
|
||||
## Remarks
|
||||
|
||||
RAM cost: 0 GB
|
||||
|
||||
|
||||
@@ -11,8 +11,3 @@ Namespace for [Stanek](./bitburner.stanek.md) functions. Contains spoilers.
|
||||
```typescript
|
||||
readonly stanek: Stanek;
|
||||
```
|
||||
|
||||
## Remarks
|
||||
|
||||
RAM cost: 0 GB
|
||||
|
||||
|
||||
@@ -11,8 +11,3 @@ Namespace for [stock](./bitburner.stock.md) functions.
|
||||
```typescript
|
||||
readonly stock: Stock;
|
||||
```
|
||||
|
||||
## Remarks
|
||||
|
||||
RAM cost: 0 GB
|
||||
|
||||
|
||||
@@ -11,8 +11,3 @@ Namespace for [user interface](./bitburner.userinterface.md) functions.
|
||||
```typescript
|
||||
readonly ui: UserInterface;
|
||||
```
|
||||
|
||||
## Remarks
|
||||
|
||||
RAM cost: 0 GB
|
||||
|
||||
|
||||
@@ -454,7 +454,7 @@
|
||||
"CHALLENGE_BN9": {
|
||||
"ID": "CHALLENGE_BN9",
|
||||
"Name": "BN9: Challenge",
|
||||
"Description": "Destroy BN9 without using hacknet servers."
|
||||
"Description": "Destroy BN9 without using hacknet servers or hacknet nodes."
|
||||
},
|
||||
"CHALLENGE_BN10": {
|
||||
"ID": "CHALLENGE_BN10",
|
||||
|
||||
@@ -4,19 +4,10 @@ import { AchievementList } from "./AchievementList";
|
||||
import { achievements } from "./Achievements";
|
||||
import { Box, Typography } from "@mui/material";
|
||||
import { Player } from "@player";
|
||||
import { makeStyles } from "tss-react/mui";
|
||||
|
||||
const useStyles = makeStyles()({
|
||||
root: {
|
||||
width: 50,
|
||||
userSelect: "none",
|
||||
},
|
||||
});
|
||||
|
||||
export function AchievementsRoot(): JSX.Element {
|
||||
const { classes } = useStyles();
|
||||
return (
|
||||
<div className={classes.root} style={{ width: "100%" }}>
|
||||
<div style={{ width: "100%" }}>
|
||||
<Typography variant="h4">Achievements</Typography>
|
||||
<Box mx={2}>
|
||||
<Typography>
|
||||
|
||||
@@ -93,7 +93,7 @@ function applyAliases(origCommand: string, depth = 0, currentlyProcessingAliases
|
||||
// First get non-global aliases, and recursively apply them
|
||||
// (unless there are any reference loops or the reference chain is too deep)
|
||||
const localAlias = Aliases.get(commandArray[0]);
|
||||
if (localAlias && !currentlyProcessingAliases.includes(localAlias)) {
|
||||
if (localAlias && !currentlyProcessingAliases.includes(commandArray[0])) {
|
||||
const appliedAlias = applyAliases(localAlias, depth + 1, [commandArray[0], ...currentlyProcessingAliases]);
|
||||
commandArray.splice(0, 1, ...appliedAlias.split(" "));
|
||||
}
|
||||
@@ -101,7 +101,7 @@ function applyAliases(origCommand: string, depth = 0, currentlyProcessingAliases
|
||||
// Once local aliasing is complete (or if none are present) handle any global aliases
|
||||
const processedCommands = commandArray.reduce((resolvedCommandArray: string[], command) => {
|
||||
const globalAlias = GlobalAliases.get(command);
|
||||
if (globalAlias && !currentlyProcessingAliases.includes(globalAlias)) {
|
||||
if (globalAlias && !currentlyProcessingAliases.includes(command)) {
|
||||
const appliedAlias = applyAliases(globalAlias, depth + 1, [command, ...currentlyProcessingAliases]);
|
||||
resolvedCommandArray.push(appliedAlias);
|
||||
} else {
|
||||
|
||||
@@ -93,3 +93,12 @@ export function finishBitNode() {
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ import { StatsRow } from "../../ui/React/StatsRow";
|
||||
import { defaultMultipliers, getBitNodeMultipliers } from "../BitNode";
|
||||
import { BitNodeMultipliers } from "../BitNodeMultipliers";
|
||||
import { PartialRecord, getRecordEntries } from "../../Types/Record";
|
||||
import { canAccessBitNodeFeature } from "../BitNodeUtils";
|
||||
import { canAccessBitNodeFeature, getBitNodeLevel } from "../BitNodeUtils";
|
||||
|
||||
interface IProps {
|
||||
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
|
||||
// current node's source file, so we get the min of that, the SF's max level,
|
||||
// or if it's BN12, ∞
|
||||
const maxSfLevel = n === 12 ? Number.MAX_VALUE : 3;
|
||||
const mults = getBitNodeMultipliers(n, level ?? Math.min(Player.activeSourceFileLvl(n) + 1, maxSfLevel));
|
||||
const mults = getBitNodeMultipliers(n, level ?? getBitNodeLevel(n));
|
||||
|
||||
return (
|
||||
<Box sx={{ columnCount: 2, columnGap: 1, mb: n === 1 ? 0 : -2 }}>
|
||||
|
||||
@@ -10,6 +10,7 @@ import Button from "@mui/material/Button";
|
||||
import { BitNodeMultiplierDescription } from "./BitnodeMultipliersDescription";
|
||||
import { BitNodeAdvancedOptions } from "./BitNodeAdvancedOptions";
|
||||
import { JSONMap } from "../../Types/Jsonable";
|
||||
import { getBitNodeLevel } from "../BitNodeUtils";
|
||||
|
||||
interface IProps {
|
||||
open: boolean;
|
||||
@@ -37,7 +38,7 @@ export function PortalModal(props: IProps): React.ReactElement {
|
||||
const bitNode = BitNodes[bitNodeKey];
|
||||
if (bitNode == null) throw new Error(`Could not find BitNode object for number: ${props.n}`);
|
||||
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);
|
||||
if (!props.flume) {
|
||||
|
||||
@@ -81,6 +81,9 @@ export class Skill {
|
||||
}
|
||||
|
||||
calculateMaxUpgradeCount(currentLevel: number, cost: PositiveNumber): number {
|
||||
// At extreme levels, floating-point precision loss makes currentLevel + 1 === currentLevel,
|
||||
// causing calculateCost to return 0. No upgrade is possible in this case.
|
||||
if (this.calculateCost(currentLevel, 1 as PositiveInteger) <= 0) return 0;
|
||||
/**
|
||||
* Define:
|
||||
* - x = count
|
||||
|
||||
@@ -3,7 +3,7 @@ import type { Bladeburner } from "../Bladeburner";
|
||||
import React, { useMemo } from "react";
|
||||
import { CopyableText } from "../../ui/React/CopyableText";
|
||||
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 CloseIcon from "@mui/icons-material/Close";
|
||||
import { Skill } from "../Skill";
|
||||
@@ -18,10 +18,8 @@ export function SkillElem({ skill, bladeburner, onUpgrade }: SkillElemProps): Re
|
||||
const skillName = skill.name;
|
||||
const skillLevel = bladeburner.getSkillLevel(skillName);
|
||||
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.
|
||||
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
|
||||
* 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 }}>
|
||||
<Box display="flex" flexDirection="row" alignItems="center">
|
||||
<CopyableText variant="h6" color="primary" value={skillName} />
|
||||
{!canLevel ? (
|
||||
<IconButton disabled>
|
||||
<CloseIcon />
|
||||
</IconButton>
|
||||
{!check.available ? (
|
||||
<Tooltip title={check.error}>
|
||||
<span>
|
||||
<IconButton disabled>
|
||||
<CloseIcon />
|
||||
</IconButton>
|
||||
</span>
|
||||
</Tooltip>
|
||||
) : (
|
||||
<IconButton onClick={onClick}>
|
||||
<AddIcon />
|
||||
@@ -51,7 +53,7 @@ export function SkillElem({ skill, bladeburner, onUpgrade }: SkillElemProps): Re
|
||||
{maxLvl ? (
|
||||
<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>
|
||||
</Paper>
|
||||
|
||||
@@ -15,11 +15,11 @@ import { getRandomAlphanumericString } from "../utils/StringHelperFunctions";
|
||||
|
||||
export function tryGeneratingRandomContract(numberOfTries: number): void {
|
||||
/**
|
||||
* We try to generate a contract every 10 minutes. 525600 is the number of tries in 10 years. There is no reason to
|
||||
* support anything above that. We tested this number (525600) on a very old machine. It took only 300-350ms to
|
||||
* loop 525600 times and generate ~9137 contracts on that machine.
|
||||
* We try to generate contracts three times every 10 minutes. 1576800 is the number of tries in 10 years. There is no
|
||||
* reason to support anything above that. We tested this number (1576800) on a very old machine. It took only ~300ms
|
||||
* to loop 1576800 times and generate ~10103 contracts on that machine.
|
||||
*/
|
||||
numberOfTries = clampNumber(Math.floor(numberOfTries), 0, 525600);
|
||||
numberOfTries = clampNumber(Math.floor(numberOfTries), 0, 1576800);
|
||||
if (numberOfTries < 1) {
|
||||
return;
|
||||
}
|
||||
@@ -47,19 +47,19 @@ export function tryGeneratingRandomContract(numberOfTries: number): void {
|
||||
* - If the offline time is unusually large (being offline for years, editing save file, tampering function prototype,
|
||||
* etc.), the game will not hang when it tries to generate contracts.
|
||||
*
|
||||
* These are some data for reference:
|
||||
* - 1 month: ~1077 contracts.
|
||||
* - 3 months: ~3157 contracts.
|
||||
* - 6 months: ~5296 contracts.
|
||||
* - 12 months: ~6678 contracts.
|
||||
* - 2 years: ~7570 contracts.
|
||||
* - 5 years: ~8504 contracts.
|
||||
* - 10 years: ~9137 contracts.
|
||||
* - 25 years: ~9936 contracts.
|
||||
* - 50 years: ~10526 contracts.
|
||||
* These are some data points for reference:
|
||||
* - 1 month: ~3157 contracts.
|
||||
* - 3 months: ~6198 contracts.
|
||||
* - 6 months: ~7231 contracts.
|
||||
* - 12 months: ~8003 contracts.
|
||||
* - 2 years: ~8687 contracts.
|
||||
* - 5 years: ~9506 contracts.
|
||||
* - 10 years: ~10103 contracts.
|
||||
* - 25 years: ~10879 contracts.
|
||||
* - 50 years: ~11461 contracts.
|
||||
*
|
||||
* Those numbers mean: If the player does not have any contracts and is online (or loads a save file with equivalent
|
||||
* offline time) for X months/years, they will have ~Y contracts.
|
||||
* Those numbers mean that if the player does not have any contracts and is online (or loads a save file with
|
||||
* equivalent offline time) for X months/years, they will have ~Y contracts.
|
||||
*/
|
||||
if (random > 100 / (399 + Math.exp(0.0012 * currentNumberOfContracts))) {
|
||||
continue;
|
||||
|
||||
@@ -83,6 +83,41 @@ export function removeBracketsFromArrayString(str: string): string {
|
||||
return strCpy;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function only performs very simple checks to add the outermost optional brackets. Callers must perform other
|
||||
* preprocessing steps and postprocessing validations. For example:
|
||||
* - "[ [0, 1]]" will be incorrectly wrapped and converted to "[[[0,1]]]". Callers need to remove redundant whitespace.
|
||||
* - "[[1,2],3]" is not an array of arrays, but this function will return it as is. Callers need to call validateAnswer.
|
||||
*
|
||||
* Note:
|
||||
* - "" will always be converted to an empty array ([]).
|
||||
* - When parsing an array of arrays (isArrayOfArray = true), "[]" will be converted to an empty array ([]), not an
|
||||
* array containing an empty array ([[]]).
|
||||
*/
|
||||
export function parseArrayString(answer: string, isArrayOfArray = false): unknown {
|
||||
let modifiedAnswer = answer.trim();
|
||||
|
||||
if (isArrayOfArray && modifiedAnswer === "[]") {
|
||||
return [];
|
||||
}
|
||||
|
||||
// If it doesn't start with any bracket, it's definitely "naked".
|
||||
if (!modifiedAnswer.startsWith("[")) {
|
||||
modifiedAnswer = `[${modifiedAnswer}]`;
|
||||
} else if (isArrayOfArray && !modifiedAnswer.startsWith("[[")) {
|
||||
// If it's supposed to be an array of arrays but only has one "[".
|
||||
modifiedAnswer = `[${modifiedAnswer}]`;
|
||||
}
|
||||
|
||||
try {
|
||||
return JSON.parse(modifiedAnswer);
|
||||
} catch (error) {
|
||||
console.error(`Invalid answer: ${answer}`);
|
||||
console.error(error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export function removeQuotesFromString(str: string): string {
|
||||
let strCpy: string = str;
|
||||
if (strCpy.startsWith('"') || strCpy.startsWith("'")) {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { filterTruthy } from "../../utils/helpers/ArrayHelpers";
|
||||
import { exceptionAlert } from "../../utils/helpers/exceptionAlert";
|
||||
import { getRandomIntInclusive } from "../../utils/helpers/getRandomIntInclusive";
|
||||
import { CodingContractTypes, removeBracketsFromArrayString, removeQuotesFromString } from "../ContractTypes";
|
||||
import { CodingContractTypes, parseArrayString } from "../ContractTypes";
|
||||
import { CodingContractName } from "@enums";
|
||||
|
||||
export const findAllValidMathExpressions: Pick<CodingContractTypes, CodingContractName.FindAllValidMathExpressions> = {
|
||||
@@ -107,10 +106,12 @@ export const findAllValidMathExpressions: Pick<CodingContractTypes, CodingContra
|
||||
return result.every((sol) => solutions.has(sol));
|
||||
},
|
||||
convertAnswer: (ans) => {
|
||||
const sanitized = removeBracketsFromArrayString(ans).split(",");
|
||||
return filterTruthy(sanitized).map((s) => removeQuotesFromString(s.replace(/\s/g, "")));
|
||||
const parsedAnswer = parseArrayString(ans);
|
||||
if (!findAllValidMathExpressions[CodingContractName.FindAllValidMathExpressions].validateAnswer(parsedAnswer)) {
|
||||
return null;
|
||||
}
|
||||
return parsedAnswer;
|
||||
},
|
||||
validateAnswer: (ans): ans is string[] =>
|
||||
typeof ans === "object" && Array.isArray(ans) && ans.every((s) => typeof s === "string"),
|
||||
validateAnswer: (ans): ans is string[] => Array.isArray(ans) && ans.every((s) => typeof s === "string"),
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { CodingContractName } from "@enums";
|
||||
import { CodingContractTypes, removeBracketsFromArrayString } from "../ContractTypes";
|
||||
import { CodingContractTypes, parseArrayString } from "../ContractTypes";
|
||||
import { exceptionAlert } from "../../utils/helpers/exceptionAlert";
|
||||
import { getRandomIntInclusive } from "../../utils/helpers/getRandomIntInclusive";
|
||||
|
||||
@@ -67,10 +67,12 @@ export const generateIPAddresses: Pick<CodingContractTypes, CodingContractName.G
|
||||
return ret.length === answer.length && ret.every((ip) => answer.includes(ip));
|
||||
},
|
||||
convertAnswer: (ans) => {
|
||||
const sanitized = removeBracketsFromArrayString(ans).replace(/\s/g, "");
|
||||
return sanitized.split(",").map((ip) => ip.replace(/^(?<quote>['"])([\d.]*)\k<quote>$/g, "$2"));
|
||||
const parsedAnswer = parseArrayString(ans);
|
||||
if (!generateIPAddresses[CodingContractName.GenerateIPAddresses].validateAnswer(parsedAnswer)) {
|
||||
return null;
|
||||
}
|
||||
return parsedAnswer;
|
||||
},
|
||||
validateAnswer: (ans): ans is string[] =>
|
||||
typeof ans === "object" && Array.isArray(ans) && ans.every((s) => typeof s === "string"),
|
||||
validateAnswer: (ans): ans is string[] => Array.isArray(ans) && ans.every((s) => typeof s === "string"),
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { exceptionAlert } from "../../utils/helpers/exceptionAlert";
|
||||
import { getRandomIntInclusive } from "../../utils/helpers/getRandomIntInclusive";
|
||||
import { CodingContractTypes } from "../ContractTypes";
|
||||
import { parseArrayString, CodingContractTypes } from "../ContractTypes";
|
||||
import { CodingContractName } from "@enums";
|
||||
|
||||
export const largestRectangle: Pick<CodingContractTypes, CodingContractName.LargestRectangleInAMatrix> = {
|
||||
@@ -152,13 +152,7 @@ Answer: [[0,0],[3,1]]
|
||||
return userArea === (solution[1][0] - solution[0][0] + 1) * (solution[1][1] - solution[0][1] + 1);
|
||||
},
|
||||
convertAnswer: (ans) => {
|
||||
let parsedAnswer: unknown;
|
||||
try {
|
||||
parsedAnswer = JSON.parse(ans);
|
||||
} catch (error) {
|
||||
console.error("Invalid answer:", error);
|
||||
return null;
|
||||
}
|
||||
const parsedAnswer = parseArrayString(ans.replace(/\s/g, ""), true);
|
||||
if (!largestRectangle[CodingContractName.LargestRectangleInAMatrix].validateAnswer(parsedAnswer)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { exceptionAlert } from "../../utils/helpers/exceptionAlert";
|
||||
import { getRandomIntInclusive } from "../../utils/helpers/getRandomIntInclusive";
|
||||
import { CodingContractTypes, convert2DArrayToString, removeBracketsFromArrayString } from "../ContractTypes";
|
||||
import { CodingContractTypes, convert2DArrayToString, parseArrayString } from "../ContractTypes";
|
||||
import { CodingContractName } from "@enums";
|
||||
|
||||
export const mergeOverlappingIntervals: Pick<CodingContractTypes, CodingContractName.MergeOverlappingIntervals> = {
|
||||
@@ -70,20 +70,13 @@ export const mergeOverlappingIntervals: Pick<CodingContractTypes, CodingContract
|
||||
return result.length === answer.length && result.every((a, i) => a[0] === answer[i][0] && a[1] === answer[i][1]);
|
||||
},
|
||||
convertAnswer: (ans) => {
|
||||
const arrayRegex = /\[\d+,\d+\]/g;
|
||||
const matches = ans.replace(/\s/g, "").match(arrayRegex);
|
||||
if (matches === null) return null;
|
||||
const arr = matches.map((a) =>
|
||||
removeBracketsFromArrayString(a)
|
||||
.split(",")
|
||||
.map((n) => parseInt(n)),
|
||||
);
|
||||
// An inline function is needed here, so that TS knows this returns true if it matches the type
|
||||
if (((a: number[][]): a is [number, number][] => a.every((n) => n.length === 2))(arr)) return arr;
|
||||
return null;
|
||||
const parsedAnswer = parseArrayString(ans.replace(/\s/g, ""), true);
|
||||
if (!mergeOverlappingIntervals[CodingContractName.MergeOverlappingIntervals].validateAnswer(parsedAnswer)) {
|
||||
return null;
|
||||
}
|
||||
return parsedAnswer;
|
||||
},
|
||||
validateAnswer: (ans): ans is [number, number][] =>
|
||||
typeof ans === "object" &&
|
||||
Array.isArray(ans) &&
|
||||
ans.every((a) => Array.isArray(a) && a.length === 2 && a.every((n) => typeof n === "number")),
|
||||
},
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { CodingContractTypes, removeBracketsFromArrayString } from "../ContractTypes";
|
||||
import { CodingContractTypes, parseArrayString } from "../ContractTypes";
|
||||
import { CodingContractName } from "@enums";
|
||||
|
||||
export const proper2ColoringOfAGraph: Pick<CodingContractTypes, CodingContractName.Proper2ColoringOfAGraph> = {
|
||||
@@ -121,14 +121,12 @@ export const proper2ColoringOfAGraph: Pick<CodingContractTypes, CodingContractNa
|
||||
return data[1].every(([a, b]) => answer[a] !== answer[b]);
|
||||
},
|
||||
convertAnswer: (ans) => {
|
||||
const sanitized = removeBracketsFromArrayString(ans).replace(/\s/g, "");
|
||||
if (sanitized === "") return [];
|
||||
const arr = sanitized.split(",").map((s) => parseInt(s, 10));
|
||||
// An inline function is needed here, so that TS knows this returns true if it matches the type
|
||||
if (((a): a is (1 | 0)[] => !a.some((n) => n !== 1 && n !== 0))(arr)) return arr;
|
||||
return null;
|
||||
const parsedAnswer = parseArrayString(ans.replace(/\s/g, ""));
|
||||
if (!proper2ColoringOfAGraph[CodingContractName.Proper2ColoringOfAGraph].validateAnswer(parsedAnswer)) {
|
||||
return null;
|
||||
}
|
||||
return parsedAnswer;
|
||||
},
|
||||
validateAnswer: (ans): ans is (1 | 0)[] =>
|
||||
typeof ans === "object" && Array.isArray(ans) && !ans.some((a) => a !== 1 && a !== 0),
|
||||
validateAnswer: (ans): ans is (1 | 0)[] => Array.isArray(ans) && !ans.some((a) => a !== 1 && a !== 0),
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { exceptionAlert } from "../../utils/helpers/exceptionAlert";
|
||||
import { getRandomIntInclusive } from "../../utils/helpers/getRandomIntInclusive";
|
||||
import { CodingContractTypes, removeBracketsFromArrayString, removeQuotesFromString } from "../ContractTypes";
|
||||
import { CodingContractTypes, parseArrayString } from "../ContractTypes";
|
||||
import { CodingContractName } from "@enums";
|
||||
|
||||
export const sanitizeParenthesesInExpression: Pick<
|
||||
@@ -113,10 +113,16 @@ export const sanitizeParenthesesInExpression: Pick<
|
||||
return res.every((sol) => answer.includes(sol));
|
||||
},
|
||||
convertAnswer: (ans) => {
|
||||
const sanitized = removeBracketsFromArrayString(ans).split(",");
|
||||
return sanitized.map((s) => removeQuotesFromString(s.replace(/\s/g, "")));
|
||||
const parsedAnswer = parseArrayString(ans);
|
||||
if (
|
||||
!sanitizeParenthesesInExpression[CodingContractName.SanitizeParenthesesInExpression].validateAnswer(
|
||||
parsedAnswer,
|
||||
)
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
return parsedAnswer;
|
||||
},
|
||||
validateAnswer: (ans): ans is string[] =>
|
||||
typeof ans === "object" && Array.isArray(ans) && ans.every((s) => typeof s === "string"),
|
||||
validateAnswer: (ans): ans is string[] => Array.isArray(ans) && ans.every((s) => typeof s === "string"),
|
||||
},
|
||||
};
|
||||
|
||||
@@ -24,7 +24,7 @@ export const shortestPathInAGrid: Pick<CodingContractTypes, CodingContractName.S
|
||||
" [[0,1],\n",
|
||||
" [1,0]]\n",
|
||||
"\n",
|
||||
"Answer: ''",
|
||||
`Answer: ""`,
|
||||
].join(" ");
|
||||
},
|
||||
difficulty: 7,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { CodingContractName } from "@enums";
|
||||
import { removeBracketsFromArrayString, type CodingContractTypes } from "../ContractTypes";
|
||||
import { parseArrayString, type CodingContractTypes } from "../ContractTypes";
|
||||
import { exceptionAlert } from "../../utils/helpers/exceptionAlert";
|
||||
import { getRandomIntInclusive } from "../../utils/helpers/getRandomIntInclusive";
|
||||
|
||||
@@ -127,10 +127,12 @@ export const spiralizeMatrix: Pick<CodingContractTypes, CodingContractName.Spira
|
||||
return spiral.length === answer.length && spiral.every((n, i) => n === answer[i]);
|
||||
},
|
||||
convertAnswer: (ans) => {
|
||||
const sanitized = removeBracketsFromArrayString(ans).replace(/\s/g, "").split(",");
|
||||
return sanitized.map((s) => parseInt(s));
|
||||
const parsedAnswer = parseArrayString(ans);
|
||||
if (!spiralizeMatrix[CodingContractName.SpiralizeMatrix].validateAnswer(parsedAnswer)) {
|
||||
return null;
|
||||
}
|
||||
return parsedAnswer;
|
||||
},
|
||||
validateAnswer: (ans): ans is number[] =>
|
||||
typeof ans === "object" && Array.isArray(ans) && ans.every((n) => typeof n === "number"),
|
||||
validateAnswer: (ans): ans is number[] => Array.isArray(ans) && ans.every((n) => typeof n === "number"),
|
||||
},
|
||||
};
|
||||
|
||||
@@ -7,7 +7,7 @@ export const CONSTANTS = {
|
||||
VersionString: "3.0.0dev",
|
||||
isDevBranch: true,
|
||||
isInTestEnvironment: globalThis.process?.env?.JEST_WORKER_ID !== undefined,
|
||||
VersionNumber: 47,
|
||||
VersionNumber: 49,
|
||||
|
||||
/** 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
|
||||
@@ -111,7 +111,7 @@ export const CONSTANTS = {
|
||||
|
||||
// Also update Documentation/doc/en/changelog.md when appropriate (when doing a release)
|
||||
LatestUpdate: `
|
||||
## v3.0.0 development version: last updated 18 February 2026
|
||||
## v3.0.0 development version: last updated 13 April 2026
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
@@ -140,6 +140,10 @@ export const CONSTANTS = {
|
||||
- Generate test contracts on executing host by default. Add support for optional parameter to specify the server (#2417) (@1337JiveTurkey)
|
||||
- The "darkweb" server becomes a darknet server (Check new Dark Net feature in #2139) (@ficocelliguy)
|
||||
- Remove RAM cost of hacknet namespace and set RAM cost of each hacknet API (#2502) (@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 implicit string conversion consistent across all coding contracts (#2608) (@catloversg)
|
||||
- Rename ns.gang.getOtherGangInformation to getAllGangInformation (#2635) (@lstutzman)
|
||||
|
||||
### MAJOR CHANGES
|
||||
|
||||
@@ -209,6 +213,22 @@ export const CONSTANTS = {
|
||||
- Improve navigation system of in-game documentation viewer (#2499) (@catloversg)
|
||||
- Use font family setting when rendering MUI Link component (#2511) (@catloversg)
|
||||
- Show hints of BitNode documentation and allow opening it in BitVerse (#2513) (@catloversg)
|
||||
- Show errors if using nano/vim with patterns that do not match any files (#2515) (@catloversg)
|
||||
- Tweak CSS/Position of Darknet Docs link (#2517) (@d0sboots)
|
||||
- Add indicator of RFA connection status to overview panel (#2497) (@catloversg)
|
||||
- Fix issues with RFA auto-reconnecting feature (#2535) (@catloversg)
|
||||
- Add inline script RAM usage text to each active script (#2546) (@vadien)
|
||||
- Update toolbar of in-game editor (#2551) (@catloversg)
|
||||
- Show "undefined" instead of -1 as pid in error popup when catching promise errors (#2555) (@catloversg)
|
||||
- Add option to autosave scripts on focus change (#2565) (@catloversg)
|
||||
- Prevent joining banned factions via UI (#2573) (@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)
|
||||
- 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
|
||||
|
||||
@@ -291,6 +311,31 @@ export const CONSTANTS = {
|
||||
- More fixes and feedback in darknet (#2489) (@ficocelliguy)
|
||||
- Fix missed cases in offline server handling (#2495) (@d0sboots)
|
||||
- Adjust darknet balance from player feedback (#2512) (@ficocelliguy)
|
||||
- Add "Find Largest Rectangle in a Matrix" coding contract (#2519) (@Misha279-UA)
|
||||
- Tweak Dnet based on player feedback (#2533, #2545, #2593) (@ficocelliguy)
|
||||
- Allow parsing unknown options with data.flags in autocomplete (#2539) (@catloversg)
|
||||
- Fix webstorm by using a mutationLock (#2542) (@d0sboots)
|
||||
- Print error message when calling ns.ui.closeTail with nonexistent pid or pid of stopped scripts (#2557) (@catloversg)
|
||||
- Add minimum width/height constraints to ns.ui.resizeTail (#2558) (@catloversg)
|
||||
- Add API to minimize and expand tail windows (#2556) (@catloversg)
|
||||
- Improve error messages for invalid sleeve numbers (#2567) (@catloversg)
|
||||
- Improve help text of expr command (#2561) (@catloversg)
|
||||
- Rework faction rumor (#2569) (@catloversg)
|
||||
- Fix: hacknetNodeCost formula API throws when using documented optional parameter (#2577) (@catloversg)
|
||||
- Rework intelligence override (#2575) (@catloversg)
|
||||
- Electron: Allow opening dev tools via CLI arguments (#2589) (@catloversg)
|
||||
- Support importing Steam Cloud save file manually (#2583) (@catloversg)
|
||||
- Electron: Add UI menus and CLI flags to change log levels (#2596) (@catloversg)
|
||||
- Import correct cloud file when multiple exist (#2599) (@catloversg)
|
||||
- Dnet: Remove packet capture (#2594) (@ficocelliguy)
|
||||
- Generate more frequent and lower-reward coding contracts (#2603) (@ficocelliguy)
|
||||
- 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
|
||||
|
||||
@@ -336,6 +381,17 @@ export const CONSTANTS = {
|
||||
- Add a note to CONTRIBUTING.md about the npm peer dep issue (#2474) (@d0sboots)
|
||||
- Update darknet documentation (#2483) (@ficocelliguy)
|
||||
- Fix invalid links in ns.sleep and ns.asleep (#2496) (@catloversg)
|
||||
- Use relative links instead of absolute links (#2521) (@catloversg)
|
||||
- Document quirky behavior of ns.flags when default value is nullish (#2528) (@catloversg)
|
||||
- Clarify how share power affects reputation gain rate of non-hacking work (#2544) (@catloversg)
|
||||
- Update guides (#2550) (@catloversg)
|
||||
- Add missing newline after RAM cost (#2570) (@catloversg)
|
||||
- Update mention of outdated getStockForecast API (#2578) (@catloversg)
|
||||
- Fix newline issues in IPvGO docs and add missing RAM cost (#2602) (@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
|
||||
|
||||
@@ -344,6 +400,11 @@ export const CONSTANTS = {
|
||||
- Fix: Bladeburner console prints main body's HP instead of sleeve's HP (#2390) (@catloversg)
|
||||
- Reduce threshold of showing warning of low population (#2450) (@catloversg)
|
||||
- Fix: Hacknet server UI shows NaN hash rate when 100% RAM is being used (#2500) (@catloversg)
|
||||
- Prevent ending BNs through reuse of Bladeburner UI event handler (#2574) (@catloversg)
|
||||
- Always show Black Operations list (#2592) (@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
|
||||
|
||||
@@ -366,6 +427,14 @@ export const CONSTANTS = {
|
||||
- Fix: Sleeves can earn exp and purchase augmentations when enabling disableSleeveExpAndAugmentation (#2467) (@catloversg)
|
||||
- Add improved challenge achievement for BN15 (#2479) (@ficocelliguy)
|
||||
- Stop randomizing Bladeburner's action difficulty (#2491) (@catloversg)
|
||||
- Adjusted Bladeburner's team bonus computation to make one member help (#2541) (@JoshuaCF)
|
||||
- Rebalance charisma exp gain of Recruitment action (#2549) (@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)
|
||||
- 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
|
||||
|
||||
@@ -382,6 +451,7 @@ export const CONSTANTS = {
|
||||
- Update type of Player.factions, GangGenInfo.faction and CorpMaterialConstantData.name (#2347) (@catloversg)
|
||||
- Clarification of Singularity costs inside BN4 (#2403) (@gmcew)
|
||||
- Update math notations in corporation docs (#2452) (@catloversg)
|
||||
- Update BitNode recommendation short guide (#2523, #2529) (@catloversg)
|
||||
|
||||
### CODEBASE/REFACTOR/WORKFLOW/JEST/TOOL/DEPS
|
||||
|
||||
@@ -449,5 +519,28 @@ export const CONSTANTS = {
|
||||
- Refactor/adjust getPixelPosition in darknet UI code (#2501) (@d0sboots)
|
||||
- Add tests for checking getAnswer and solver of coding contracts (#2503) (@catloversg)
|
||||
- Refactor ImportSave component (#2505) (@catloversg)
|
||||
- Show coding contract names when their tests failed (#2520) (@catloversg)
|
||||
- Workflow: Fix wrong instruction of generating docs (#2522) (@catloversg)
|
||||
- Update comment of LoadingScreen of ComplexPage enum (#2527) (@catloversg)
|
||||
- Make getPlayer 10x faster (#2548) (@d0sboots)
|
||||
- Create monaco editor instance with null model (#2563) (@catloversg)
|
||||
- Update dependencies (#2576) (@catloversg)
|
||||
- Remove barrel imports in Bladeburner code (#2580) (@catloversg)
|
||||
- Fix React warning in IPvGO scoring explanation popup (#2581) (@catloversg)
|
||||
- Update Babel core, presets and module loader for webpack (#2585) (@catloversg)
|
||||
- Add script to generate webpack bundle report (#2587) (@catloversg)
|
||||
- Update Electron (#2591) (@catloversg)
|
||||
- Mitigate issue of forcefullyCrashRenderer (#2597) (@catloversg)
|
||||
- Split Settings.ts to reduce number of imports (#2600) (@catloversg)
|
||||
- Remove duplicate random alphanumeric string functions (#2601) (@catloversg)
|
||||
- Update comments to reflect changes in #2603 (#2606) (@catloversg)
|
||||
- Replace ipExists() linear scan with O(1) Map.has() (#2621) (@lstutzman)
|
||||
- Refactor and fix issues in db.ts (#2623) (@catloversg)
|
||||
- Add dependency array to TerminalInput keydown useEffect (#2620) (@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;
|
||||
|
||||
@@ -210,7 +210,7 @@ export const getTimingAttackConfig = (difficulty: number): ServerConfig => {
|
||||
"I spent some time on it, but that's not the password",
|
||||
];
|
||||
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 {
|
||||
modelId: ModelIds.TimingAttack,
|
||||
password: getPassword(length, alphanumeric),
|
||||
|
||||
@@ -79,17 +79,9 @@ export const calculateAuthenticationTime = (
|
||||
const underleveledFactor = applyUnderleveledFactor ? 1.5 + (chaRequired + 50) / (person.skills.charisma + 50) : 1;
|
||||
const hasBootsFactor = Player.hasAugmentation(AugmentationName.TheBoots) ? 0.8 : 1;
|
||||
const hasSf15_2Factor = Player.activeSourceFileLvl(15) > 2 ? 0.8 : 1;
|
||||
const bonusTimeFactor = hasDarknetBonusTime() ? 0.75 : 1;
|
||||
|
||||
const time =
|
||||
baseTime *
|
||||
skillFactor *
|
||||
backdoorFactor *
|
||||
underleveledFactor *
|
||||
hasBootsFactor *
|
||||
hasSf15_2Factor *
|
||||
bonusTimeFactor *
|
||||
threadsFactor;
|
||||
baseTime * skillFactor * backdoorFactor * underleveledFactor * hasBootsFactor * hasSf15_2Factor * threadsFactor;
|
||||
|
||||
// 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).
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Player } from "@player";
|
||||
import { addClue } from "./effects";
|
||||
import { formatNumber } from "../../ui/formatNumber";
|
||||
import { formatNumber, formatRam } from "../../ui/formatNumber";
|
||||
import { logger } from "./offlineServerHandling";
|
||||
import type { NetscriptContext } from "../../Netscript/APIWrapper";
|
||||
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 { ResponseCodeEnum } from "../Enums";
|
||||
import { isLabyrinthServer } from "./labyrinth";
|
||||
import { roundToTwo } from "../../utils/helpers/roundToTwo";
|
||||
|
||||
/*
|
||||
* 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 ramBlockRemoved = getRamBlockRemoved(server, threads);
|
||||
server.blockedRam -= ramBlockRemoved;
|
||||
server.blockedRam = roundToTwo(server.blockedRam - ramBlockRemoved);
|
||||
server.updateRamUsed(server.ramUsed - ramBlockRemoved);
|
||||
|
||||
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;
|
||||
Player.gainCharismaExp(xpGained);
|
||||
|
||||
const result = `Liberated ${formatNumber(
|
||||
const result = `Liberated ${formatRam(
|
||||
ramBlockRemoved,
|
||||
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);
|
||||
return {
|
||||
success: true,
|
||||
@@ -72,7 +73,7 @@ export const getRamBlockRemoved = (darknetServerData: DarknetServerData, threads
|
||||
const charismaFactor = 1 + player.skills.charisma / 100;
|
||||
const difficultyFactor = 2 * 0.92 ** (difficulty + 1);
|
||||
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 [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)]);
|
||||
};
|
||||
|
||||
@@ -14,6 +14,7 @@ import { validBitNodes } from "../../BitNode/Constants";
|
||||
import { DeleteServer, GetAllServers } from "../../Server/AllServers";
|
||||
import { HacknetServer } from "../../Hacknet/HacknetServer";
|
||||
import { AutoExpandAccordion } from "../../ui/AutoExpand/AutoExpandAccordion";
|
||||
import { getDarkscapeNavigator } from "../../DarkNet/effects/effects";
|
||||
|
||||
const useStyles = makeStyles()({
|
||||
group: {
|
||||
@@ -46,6 +47,9 @@ export function SourceFilesDev({ parentRerender }: { parentRerender: () => void
|
||||
Player.hacknetNodes = Player.hacknetNodes.filter((node) => typeof node === "string");
|
||||
}
|
||||
}
|
||||
if (sfN === 15 && sfLvl !== 0) {
|
||||
getDarkscapeNavigator();
|
||||
}
|
||||
if (sfLvl === 0) {
|
||||
Player.sourceFiles.delete(sfN);
|
||||
Player.bitNodeOptions.sourceFileOverrides.delete(sfN);
|
||||
|
||||
@@ -6,11 +6,29 @@ Coding Contracts are files with the `.cct` extension.
|
||||
They can be accessed through the [Terminal](terminal.md) or through [Scripts](scripts.md) using the [Coding Contract API](../../../../../markdown/bitburner.codingcontract.md).
|
||||
|
||||
Each contract has a limited number of attempts.
|
||||
If you provide the wrong answer too many times and exceed the number of attempts, the contract will self destruct (delete itself).
|
||||
If you provide the wrong answer too many times and exceed the number of attempts, the contract will self-destruct (delete itself).
|
||||
|
||||
Coding Contracts are randomly generated and spawn over time. Initially, you'll only see a small range of the easier contracts, but as you progress further through the game more challenging ones will unlock.
|
||||
They can appear on any [server](servers.md) (including your home computer), except for your purchased [servers](servers.md).
|
||||
|
||||
## Contract generation
|
||||
|
||||
### Online
|
||||
|
||||
Every 10 minutes, the game makes three independent attempts to generate a contract on normal servers, each with a base
|
||||
25% chance. This probability decreases based on the total number of contracts across all servers. In most cases, you can
|
||||
assume a ~25% success rate per attempt.
|
||||
|
||||
### Offline
|
||||
|
||||
When the game is launched after being offline, the offline time is used to calculate the number of generation attempts.
|
||||
The same rules and probabilities from the online generation process apply.
|
||||
|
||||
### Dark Net
|
||||
|
||||
Opening cache files on [darknet](../programming/darknet.md) servers also has a chance to generate a contract, but with
|
||||
lower rewards. Contracts generated this way grant 50% lower rewards than those generated randomly on normal servers.
|
||||
|
||||
## Running in Terminal
|
||||
|
||||
To run a Coding Contract in the [Terminal](terminal.md), simply use the `run` command:
|
||||
@@ -40,24 +58,92 @@ The [`getContractTypes`](../../../../../markdown/bitburner.codingcontract.getcon
|
||||
|
||||
## Submitting Solutions
|
||||
|
||||
### General rules
|
||||
|
||||
Different contract problem types will require different types of solutions.
|
||||
Some may be numbers, others may be strings or arrays.
|
||||
|
||||
If a contract asks for a specific solution format, then use that.
|
||||
Otherwise, follow these rules when submitting solutions:
|
||||
|
||||
- String-type solutions should **not** have quotation marks surrounding the string (unless specifically asked for).
|
||||
- String-type solutions (e.g., Shortest Path in a Grid) should **not** have quotation marks surrounding the string (unless specifically asked for). For example, if your answer is `foo` (3 characters: f, o, o), just submit those 3 characters. Don't submit `"foo"` (5 characters).
|
||||
Only quotation marks that are part of the actual string solution should be included.
|
||||
- With array-of-strings solutions (e.g., Generate IP Addresses), you need to use double quotes surrounding the string values. Don't use single quotes (`''`) or backticks (\`\`). For example, if your answer is an array containing `foo` (3 characters: f, o, o) and `bar` (3 characters: b, a, r), you should submit `["foo", "bar"]`. Don't submit `['foo', 'bar']`.
|
||||
- Array-type solutions should be submitted with each element in the array separated by commas.
|
||||
Brackets are optional.
|
||||
For example, both of the following are valid solution formats:
|
||||
- `1,2,3`
|
||||
- `[1,2,3]`
|
||||
- If the solution is a multidimensional array, then all arrays that are not the outer-most array DO require the brackets.
|
||||
For example, an array of arrays can be submitted as one of the following:
|
||||
- Numeric solutions should be submitted normally, as expected.
|
||||
- Read the description carefully. Some contracts (e.g., the "Square Root" contract) clearly specify the expected solution format.
|
||||
- If the solution format is not a string, you should not convert the answer to a string. Read the next sections carefully if you do so.
|
||||
|
||||
### String conversion
|
||||
|
||||
For convenience (e.g., submitting the answer via the UI) and backward compatibility, the game accepts a string answer even when
|
||||
the solution format is not a string. In these cases, the game converts your string answer to the expected format. However,
|
||||
this conversion has many pitfalls.
|
||||
|
||||
String conversion only matters when you submit the answer via the UI (your answer, typed in the text box, is always a string). When you call the `ns.codingcontract.attempt` API, you should never convert your non-string answer to a string unless specifically asked for.
|
||||
|
||||
First, with arrays, the outermost pair of brackets is optional. For example, both of the following are valid solution formats:
|
||||
|
||||
- `1,2,3`
|
||||
- `[1,2,3]`
|
||||
|
||||
Note:
|
||||
|
||||
- If the solution is a multidimensional array, then all arrays that are not the outermost array DO require the brackets. For example, an array of arrays can be submitted as one of the following:
|
||||
- `[1,2],[3,4]`
|
||||
- `[[1,2],[3,4]]`
|
||||
- The empty string is converted to an empty array.
|
||||
- `"[]"` (the string that contains only 2 bracket characters; the double quotes are not part of that string) is converted to an empty array.
|
||||
|
||||
Numeric solutions should be submitted normally, as expected
|
||||
Second, in the UI:
|
||||
|
||||
- If your answer is an empty string, you must leave the text box empty. Do NOT use `""`, `''`, or \`\`.
|
||||
- If the answer is a non-empty string, type it as is. For example, if your answer is the word `foo`, type `foo` (3 characters: f, o, o). Do NOT add any types of quotes.
|
||||
- If the answer is an array that contains strings, use double quotes for strings. Do NOT use single quotes or backticks. For example, if your answer is an array containing the word `foo`, type `["foo"]` (7 characters: square bracket, double quote, f, o, o, double quote, square bracket). The brackets are optional, as stated above, but we recommend including them.
|
||||
|
||||
### Tips
|
||||
|
||||
If a contract does not expect a string, you should not submit a string. For contracts that do not expect a string
|
||||
solution, your answer should never be a string, so if you submit a string, it means that you converted your non-string
|
||||
answer to a string. This is usually the wrong thing to do.
|
||||
|
||||
Remember, string conversion is for UI convenience and backward compatibility. If you use NS APIs, do not perform any
|
||||
string conversion unless specifically asked for.
|
||||
|
||||
For example, suppose a contract requires the answer to be an array containing strings, and you determine that those
|
||||
strings are `foo` and `bar`. Your code should look like this:
|
||||
|
||||
```js
|
||||
const firstString = "foo";
|
||||
const secondString = "bar";
|
||||
const answer = [firstString, secondString];
|
||||
ns.codingcontract.attempt(answer, "filename.cct");
|
||||
```
|
||||
|
||||
There is no conversion!
|
||||
|
||||
In the "General rules" section above, with array-of-strings solutions, we say `Don't use single quotes or backticks`.
|
||||
However, this code works:
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
```js
|
||||
const firstString = 'foo'; // Single quotes
|
||||
const secondString = 'bar'; // Single quotes
|
||||
const answer = [firstString, secondString];
|
||||
ns.codingcontract.attempt(answer, "filename.cct");
|
||||
```
|
||||
|
||||
Why is that?
|
||||
|
||||
In this code, you submit an array containing 2 strings. In JS, `"foo"` and `'foo'` are the same string. However, if you
|
||||
submit your answer as a string, you need to convert your array to a string, and the string `["foo", "bar"]` is not the
|
||||
same as the string `['foo', 'bar']`.
|
||||
|
||||
Internally, we use `JSON.parse` to convert the string answer, and `['foo', 'bar']` is not a valid string representation
|
||||
of an array. In JSON, a string needs to be enclosed by double quotes. Using single quotes or backticks is not allowed.
|
||||
|
||||
This is another example of why you should not convert your answer to a string when not requested. If you submit your
|
||||
array as it is, you do not need to care about the quote types.
|
||||
|
||||
## Rewards
|
||||
|
||||
@@ -68,7 +154,18 @@ There are currently four possible rewards for solving a Coding Contract:
|
||||
- [Company](companies.md) [Reputation](reputation.md) for a specific [Company](companies.md)
|
||||
- Money
|
||||
|
||||
The `amount` of the reward varies based on the difficulty of the problem posed by the Coding Contract.
|
||||
The reward type is randomly chosen at spawn time. If the chosen reward is invalid upon completion (e.g., requirements
|
||||
are not met), it falls back to an alternative type:
|
||||
|
||||
- Specific faction reputation ⇒ Money
|
||||
- All factions' reputation ⇒ Money
|
||||
- Company reputation ⇒ Specific faction reputation or all factions' reputation (50% chance for each).
|
||||
If the fallback reward is also invalid, the reward defaults to Money.
|
||||
|
||||
For example, if a contract is set to reward "All Factions' Reputation" but you have not joined any factions at the time
|
||||
of submission, you will receive Money instead.
|
||||
|
||||
The amount of the reward varies based on the difficulty of the problem posed by the Coding Contract.
|
||||
|
||||
## Notes
|
||||
|
||||
|
||||
@@ -100,3 +100,9 @@ with a semicolon (;). For example:
|
||||
$ run foo.js; tail foo.js
|
||||
|
||||
Chained commands do **not** wait for functions like `hack` or `wget` to finish executing, and so may not always work as expected.
|
||||
|
||||
## Quirks
|
||||
|
||||
When your scripts render a text box (e.g., `<input>`, `<textarea>`) with the `autoFocus` attribute in the terminal or
|
||||
the tail log window, it may not focus automatically as expected. To be precise, the text box receives focus, but the
|
||||
terminal may immediately reclaim it. This depends on the specific timing of the render.
|
||||
|
||||
@@ -367,12 +367,11 @@ Paste the following code into the [Script](../basic/scripts.md) editor:
|
||||
|
||||
/** @param {NS} ns */
|
||||
export async function main(ns) {
|
||||
// How much RAM each cloud server will have. In this case, it'll
|
||||
// be 8GB.
|
||||
// How much RAM each cloud server will have. In this case, it'll be 8GB.
|
||||
const ram = 8;
|
||||
|
||||
// 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
|
||||
// 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 we have enough money, then:
|
||||
// 1. Purchase the 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
|
||||
// 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
|
||||
// 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.exec("early-hack-template.js", hostname, 3);
|
||||
++i;
|
||||
}
|
||||
//Make the script wait for a second before looping again.
|
||||
//Removing this line will cause an infinite loop and crash the game.
|
||||
// Make the script wait for a second before looping again.
|
||||
// Removing this line will cause an infinite loop and crash the game.
|
||||
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:
|
||||
|
||||
var ram = 8;
|
||||
var i = 0;
|
||||
const ram = 8;
|
||||
let i = ns.cloud.getServerNames().length;
|
||||
|
||||
while (i < ns.cloud.getServerLimit()) {
|
||||
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.exec("early-hack-template.js", hostname, 3);
|
||||
++i;
|
||||
|
||||
@@ -68,6 +68,7 @@ import file65 from "./doc/en/programming/remote_api.md?raw";
|
||||
import file66 from "./doc/en/programming/typescript_react.md?raw";
|
||||
|
||||
import nsDoc_bitburner__valueof_md from "../../markdown/bitburner._valueof.md?raw";
|
||||
import nsDoc_bitburner_activefragment_chargedeffect_md from "../../markdown/bitburner.activefragment.chargedeffect.md?raw";
|
||||
import nsDoc_bitburner_activefragment_highestcharge_md from "../../markdown/bitburner.activefragment.highestcharge.md?raw";
|
||||
import nsDoc_bitburner_activefragment_md from "../../markdown/bitburner.activefragment.md?raw";
|
||||
import nsDoc_bitburner_activefragment_numcharge_md from "../../markdown/bitburner.activefragment.numcharge.md?raw";
|
||||
@@ -560,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_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_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_getbonustime_md from "../../markdown/bitburner.gang.getbonustime.md?raw";
|
||||
import nsDoc_bitburner_gang_getchancetowinclash_md from "../../markdown/bitburner.gang.getchancetowinclash.md?raw";
|
||||
@@ -571,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_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_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_gettasknames_md from "../../markdown/bitburner.gang.gettasknames.md?raw";
|
||||
import nsDoc_bitburner_gang_gettaskstats_md from "../../markdown/bitburner.gang.gettaskstats.md?raw";
|
||||
@@ -745,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_hacktime_md from "../../markdown/bitburner.hackingformulas.hacktime.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_hackingmultipliers_chance_md from "../../markdown/bitburner.hackingmultipliers.chance.md?raw";
|
||||
import nsDoc_bitburner_hackingmultipliers_growth_md from "../../markdown/bitburner.hackingmultipliers.growth.md?raw";
|
||||
@@ -1663,6 +1665,7 @@ AllPages["en/programming/remote_api.md"] = file65;
|
||||
AllPages["en/programming/typescript_react.md"] = file66;
|
||||
|
||||
AllPages["nsDoc/bitburner._valueof.md"] = nsDoc_bitburner__valueof_md;
|
||||
AllPages["nsDoc/bitburner.activefragment.chargedeffect.md"] = nsDoc_bitburner_activefragment_chargedeffect_md;
|
||||
AllPages["nsDoc/bitburner.activefragment.highestcharge.md"] = nsDoc_bitburner_activefragment_highestcharge_md;
|
||||
AllPages["nsDoc/bitburner.activefragment.md"] = nsDoc_bitburner_activefragment_md;
|
||||
AllPages["nsDoc/bitburner.activefragment.numcharge.md"] = nsDoc_bitburner_activefragment_numcharge_md;
|
||||
@@ -2155,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.canrecruitmember.md"] = nsDoc_bitburner_gang_canrecruitmember_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.getbonustime.md"] = nsDoc_bitburner_gang_getbonustime_md;
|
||||
AllPages["nsDoc/bitburner.gang.getchancetowinclash.md"] = nsDoc_bitburner_gang_getchancetowinclash_md;
|
||||
@@ -2166,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.getmemberinformation.md"] = nsDoc_bitburner_gang_getmemberinformation_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.gettasknames.md"] = nsDoc_bitburner_gang_gettasknames_md;
|
||||
AllPages["nsDoc/bitburner.gang.gettaskstats.md"] = nsDoc_bitburner_gang_gettaskstats_md;
|
||||
@@ -2340,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.hacktime.md"] = nsDoc_bitburner_hackingformulas_hacktime_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.hackingmultipliers.chance.md"] = nsDoc_bitburner_hackingmultipliers_chance_md;
|
||||
AllPages["nsDoc/bitburner.hackingmultipliers.growth.md"] = nsDoc_bitburner_hackingmultipliers_growth_md;
|
||||
|
||||
@@ -9,6 +9,10 @@ import Tabs from "@mui/material/Tabs";
|
||||
import Tab from "@mui/material/Tab";
|
||||
|
||||
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. */
|
||||
export function GangRoot(): React.ReactElement {
|
||||
@@ -18,7 +22,7 @@ export function GangRoot(): React.ReactElement {
|
||||
})();
|
||||
const [value, setValue] = React.useState(0);
|
||||
|
||||
function handleChange(event: React.SyntheticEvent, tab: number): void {
|
||||
function handleChange(__event: React.SyntheticEvent, tab: number): void {
|
||||
setValue(tab);
|
||||
}
|
||||
|
||||
@@ -26,11 +30,26 @@ export function GangRoot(): React.ReactElement {
|
||||
|
||||
return (
|
||||
<Context.Gang.Provider value={gang}>
|
||||
<Tabs variant="fullWidth" value={value} onChange={handleChange} sx={{ minWidth: "fit-content", maxWidth: "45%" }}>
|
||||
<Tab label="Management" />
|
||||
<Tab label="Equipment" />
|
||||
<Tab label="Territory" />
|
||||
</Tabs>
|
||||
<div style={{ display: "flex" }}>
|
||||
<Tabs
|
||||
variant="fullWidth"
|
||||
value={value}
|
||||
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 === 1 && <EquipmentsSubpage />}
|
||||
{value === 2 && <TerritorySubpage />}
|
||||
|
||||
@@ -275,7 +275,7 @@ const gang = {
|
||||
getMemberNames: RamCostConstants.GangApiBase / 4,
|
||||
renameMember: 0,
|
||||
getGangInformation: RamCostConstants.GangApiBase / 2,
|
||||
getOtherGangInformation: RamCostConstants.GangApiBase / 2,
|
||||
getAllGangInformation: RamCostConstants.GangApiBase / 2,
|
||||
getMemberInformation: RamCostConstants.GangApiBase / 2,
|
||||
canRecruitMember: RamCostConstants.GangApiBase / 4,
|
||||
getRecruitsAvailable: RamCostConstants.GangApiBase / 4,
|
||||
@@ -692,6 +692,7 @@ export const RamCosts: RamCostTree<NSFull> = {
|
||||
hackTime: 0,
|
||||
growTime: 0,
|
||||
weakenTime: 0,
|
||||
weakenEffect: 0,
|
||||
},
|
||||
hacknetNodes: {
|
||||
moneyGainRate: 0,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Player } from "@player";
|
||||
import { calculateServerGrowth, calculateGrowMoney } from "../Server/formulas/grow";
|
||||
import { numCycleForGrowthCorrected } from "../Server/ServerHelpers";
|
||||
import { getWeakenEffect, numCycleForGrowthCorrected } from "../Server/ServerHelpers";
|
||||
import {
|
||||
calculateMoneyGainRate,
|
||||
calculateLevelUpgradeCost,
|
||||
@@ -235,6 +235,14 @@ export function NetscriptFormulas(): InternalAPI<IFormulas> {
|
||||
checkFormulasAccess(ctx);
|
||||
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: {
|
||||
moneyGainRate:
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { Gang as IGang, EquipmentStats, GangOtherInfoObject } from "@nsdefs
|
||||
import type { Gang } from "../Gang/Gang";
|
||||
import type { GangMember } from "../Gang/GangMember";
|
||||
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 { Player } from "@player";
|
||||
@@ -37,7 +37,7 @@ export function NetscriptGang(): InternalAPI<IGang> {
|
||||
return task;
|
||||
};
|
||||
|
||||
return {
|
||||
const gangFunctions: InternalAPI<IGang> = {
|
||||
createGang: (ctx) => (_faction) => {
|
||||
const faction = getEnumHelper("FactionName").nsGetMember(ctx, _faction);
|
||||
if (Player.gang) {
|
||||
@@ -117,7 +117,7 @@ export function NetscriptGang(): InternalAPI<IGang> {
|
||||
equipmentCostMult: 1 / gang.getDiscount(),
|
||||
};
|
||||
},
|
||||
getOtherGangInformation: (ctx) => () => {
|
||||
getAllGangInformation: (ctx) => () => {
|
||||
getGang(ctx);
|
||||
const cpy: Record<string, GangOtherInfoObject> = {};
|
||||
for (const gang of Object.keys(AllGangs)) {
|
||||
@@ -362,4 +362,10 @@ export function NetscriptGang(): InternalAPI<IGang> {
|
||||
return GangPromise.promise;
|
||||
},
|
||||
};
|
||||
|
||||
// Removed functions
|
||||
setRemovedFunctions(gangFunctions, {
|
||||
getOtherGangInformation: { version: "3.0.0", replacement: "gang.getAllGangInformation" },
|
||||
});
|
||||
return gangFunctions;
|
||||
}
|
||||
|
||||
@@ -64,7 +64,11 @@ export function NetscriptStanek(): InternalAPI<IStanek> {
|
||||
checkStanekAPIAccess(ctx);
|
||||
helpers.log(ctx, () => `Returned ${staneksGift.fragments.length} fragments`);
|
||||
return staneksGift.fragments.map((activeFragment) => {
|
||||
return { ...activeFragment.copy(), ...activeFragment.fragment().copy() };
|
||||
return {
|
||||
...activeFragment.copy(),
|
||||
...activeFragment.fragment().copy(),
|
||||
chargedEffect: staneksGift.effect(activeFragment),
|
||||
};
|
||||
}) satisfies ReturnType<IStanek["activeFragments"]>;
|
||||
},
|
||||
clearGift: (ctx) => () => {
|
||||
@@ -99,9 +103,11 @@ export function NetscriptStanek(): InternalAPI<IStanek> {
|
||||
checkStanekAPIAccess(ctx);
|
||||
const activeFragment = staneksGift.findFragment(rootX, rootY);
|
||||
if (activeFragment !== undefined) {
|
||||
return { ...activeFragment.copy(), ...activeFragment.fragment().copy() } satisfies ReturnType<
|
||||
IStanek["getFragment"]
|
||||
>;
|
||||
return {
|
||||
...activeFragment.copy(),
|
||||
...activeFragment.fragment().copy(),
|
||||
chargedEffect: staneksGift.effect(activeFragment),
|
||||
} satisfies ReturnType<IStanek["getFragment"]>;
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
|
||||
@@ -147,6 +147,10 @@ export abstract class Person implements IPerson {
|
||||
}
|
||||
|
||||
overrideIntelligence(): void {
|
||||
// Do not set anything if the player has not unlocked Intelligence.
|
||||
if (Player.sourceFileLvl(5) === 0 && Player.bitNodeN !== 5) {
|
||||
return;
|
||||
}
|
||||
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
|
||||
// than or equal to the persistent skill.
|
||||
@@ -172,7 +176,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
|
||||
* 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.skills.intelligence = Math.floor(this.calculateSkill(this.exp.intelligence, 1));
|
||||
this.persistentIntelligenceData.exp += exp;
|
||||
|
||||
@@ -602,6 +602,10 @@ export function canAccessCotMG(this: PlayerObject): boolean {
|
||||
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 {
|
||||
return this.sourceFiles.get(n) ?? 0;
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ export function getFactionFieldWorkRepGain(p: IPerson, favor: number): 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 0;
|
||||
|
||||
@@ -310,7 +310,7 @@ export function prestigeSourceFile(isFlume: boolean): void {
|
||||
}
|
||||
|
||||
// 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");
|
||||
}
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ import { loadInfiltrations } from "./Infiltration/SaveLoadInfiltration";
|
||||
import { InfiltrationState } from "./Infiltration/formulas/game";
|
||||
import { hasDarknetAccess } from "./DarkNet/utils/darknetAuthUtils";
|
||||
import { loadSettings } from "./Settings/SettingsUtils";
|
||||
import { getBitNodeLevel } from "./BitNode/BitNodeUtils";
|
||||
|
||||
/* SaveObject.js
|
||||
* 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.
|
||||
*/
|
||||
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> {
|
||||
@@ -430,7 +431,10 @@ class BitburnerSaveObject implements BitburnerSaveObjectType {
|
||||
achievements: importedPlayer.achievements?.length ?? 0,
|
||||
|
||||
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),
|
||||
exploits: importedPlayer.exploits.length,
|
||||
|
||||
@@ -453,6 +457,9 @@ class BitburnerSaveObject implements BitburnerSaveObjectType {
|
||||
async function loadGame(saveData: SaveData): Promise<boolean> {
|
||||
createScamUpdateText();
|
||||
if (!saveData) {
|
||||
console.error(
|
||||
`Invalid save data. typeof saveData: ${typeof saveData}. saveData is an empty string: ${saveData === ""}`,
|
||||
);
|
||||
return false;
|
||||
}
|
||||
const jsonSaveString = await decodeSaveData(saveData);
|
||||
|
||||
102
src/ScriptEditor/NetscriptDefinitions.d.ts
vendored
102
src/ScriptEditor/NetscriptDefinitions.d.ts
vendored
@@ -2925,7 +2925,7 @@ export interface Hacknet {
|
||||
/**
|
||||
* Get the number of hacknet nodes you own.
|
||||
* @remarks
|
||||
* RAM cost: 0 GB
|
||||
* RAM cost: 0.5 GB
|
||||
*
|
||||
* Returns the number of Hacknet Nodes you own.
|
||||
*
|
||||
@@ -2936,7 +2936,7 @@ export interface Hacknet {
|
||||
/**
|
||||
* Get the maximum number of hacknet nodes.
|
||||
* @remarks
|
||||
* RAM cost: 0 GB
|
||||
* RAM cost: 0.5 GB
|
||||
*
|
||||
* @returns Maximum number of hacknet nodes.
|
||||
*/
|
||||
@@ -2945,7 +2945,7 @@ export interface Hacknet {
|
||||
/**
|
||||
* Purchase a new hacknet node.
|
||||
* @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
|
||||
@@ -2961,7 +2961,7 @@ export interface Hacknet {
|
||||
/**
|
||||
* Get the price of the next hacknet node.
|
||||
* @remarks
|
||||
* RAM cost: 0 GB
|
||||
* RAM cost: 0.5 GB
|
||||
*
|
||||
* Returns the cost of purchasing a new Hacknet Node.
|
||||
*
|
||||
@@ -2972,7 +2972,7 @@ export interface Hacknet {
|
||||
/**
|
||||
* Get the stats of a hacknet node.
|
||||
* @remarks
|
||||
* RAM cost: 0 GB
|
||||
* RAM cost: 0.5 GB
|
||||
*
|
||||
* 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.
|
||||
* @remarks
|
||||
* RAM cost: 0 GB
|
||||
* RAM cost: 0.5 GB
|
||||
*
|
||||
* 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.
|
||||
* @remarks
|
||||
* RAM cost: 0 GB
|
||||
* RAM cost: 0.5 GB
|
||||
*
|
||||
* Tries to upgrade the specified Hacknet Node’s RAM n times.
|
||||
* Note that each upgrade doubles the Node’s RAM.
|
||||
@@ -3026,7 +3026,7 @@ export interface Hacknet {
|
||||
/**
|
||||
* Upgrade the core of a hacknet node.
|
||||
* @remarks
|
||||
* RAM cost: 0 GB
|
||||
* RAM cost: 0.5 GB
|
||||
*
|
||||
* Tries to purchase n cores for the specified Hacknet Node.
|
||||
*
|
||||
@@ -3044,7 +3044,7 @@ export interface Hacknet {
|
||||
/**
|
||||
* Upgrade the cache of a hacknet node.
|
||||
* @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).
|
||||
*
|
||||
@@ -3064,7 +3064,7 @@ export interface Hacknet {
|
||||
/**
|
||||
* Calculate the cost of upgrading hacknet node levels.
|
||||
* @remarks
|
||||
* RAM cost: 0 GB
|
||||
* RAM cost: 0.5 GB
|
||||
*
|
||||
* 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.
|
||||
* @remarks
|
||||
* RAM cost: 0 GB
|
||||
* RAM cost: 0.5 GB
|
||||
*
|
||||
* 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.
|
||||
* @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.
|
||||
*
|
||||
@@ -3112,7 +3112,7 @@ export interface Hacknet {
|
||||
/**
|
||||
* Calculate the cost of upgrading hacknet node cache.
|
||||
* @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).
|
||||
*
|
||||
@@ -3130,7 +3130,7 @@ export interface Hacknet {
|
||||
/**
|
||||
* Get the total number of hashes stored.
|
||||
* @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).
|
||||
*
|
||||
@@ -3143,7 +3143,7 @@ export interface Hacknet {
|
||||
/**
|
||||
* Get the maximum number of hashes you can store.
|
||||
* @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).
|
||||
*
|
||||
@@ -3156,7 +3156,7 @@ export interface Hacknet {
|
||||
/**
|
||||
* Get the cost of a hash upgrade.
|
||||
* @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).
|
||||
*
|
||||
@@ -3178,7 +3178,7 @@ export interface Hacknet {
|
||||
/**
|
||||
* Purchase a hash upgrade.
|
||||
* @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).
|
||||
*
|
||||
@@ -3207,7 +3207,7 @@ export interface Hacknet {
|
||||
/**
|
||||
* Get the list of hash upgrades
|
||||
* @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).
|
||||
*
|
||||
@@ -3223,7 +3223,7 @@ export interface Hacknet {
|
||||
/**
|
||||
* Get the level of a hash upgrade.
|
||||
* @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).
|
||||
*
|
||||
@@ -3234,7 +3234,7 @@ export interface Hacknet {
|
||||
/**
|
||||
* Get the multiplier to study.
|
||||
* @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).
|
||||
*
|
||||
@@ -3245,7 +3245,7 @@ export interface Hacknet {
|
||||
/**
|
||||
* Get the multiplier to training.
|
||||
* @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).
|
||||
*
|
||||
@@ -4879,7 +4879,7 @@ export interface Gang {
|
||||
*
|
||||
* @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.
|
||||
@@ -6299,6 +6299,16 @@ interface HackingFormulas {
|
||||
* @returns The calculated weaken time, in milliseconds.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -6586,6 +6596,21 @@ interface ActiveFragment extends Fragment {
|
||||
rotation: number;
|
||||
x: number;
|
||||
y: number;
|
||||
/**
|
||||
* This is the raw value of the modifier used to calculate the effect on your multipliers. It may not be a multiplier.
|
||||
*
|
||||
* With fragments that increase a multiplier, this value is a multiplier. For example, with "+x% hacknet production"
|
||||
* fragment, a value of 1.25 will multiply the "hacknet_node_money" multiplier by 1.25. The UI will show "+25% hacknet
|
||||
* production".
|
||||
*
|
||||
* With fragments that decrease a multiplier, you need to invert this value. For example, with "-x% cheaper hacknet
|
||||
* costs" fragment, a value of 1.25 means the "hacknet_node_purchase_cost" (and other relevant cost multipliers) will
|
||||
* be multiplied by 0.8 (1 / 1.25). The UI will show "20% cheaper hacknet costs".
|
||||
*
|
||||
* With booster fragments, this value is always 1. Booster fragments only boost non-booster fragments. They don't
|
||||
* directly boost your multipliers.
|
||||
*/
|
||||
chargedEffect: number;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -6999,113 +7024,93 @@ interface UserInterface {
|
||||
export interface NS {
|
||||
/**
|
||||
* Namespace for {@link Hacknet | hacknet} functions. Some of this API contains spoilers.
|
||||
* @remarks RAM cost: 4 GB.
|
||||
*/
|
||||
readonly hacknet: Hacknet;
|
||||
|
||||
/**
|
||||
* Namespace for {@link Bladeburner | Bladeburner} functions. Contains spoilers.
|
||||
* @remarks RAM cost: 0 GB
|
||||
*/
|
||||
readonly bladeburner: Bladeburner;
|
||||
|
||||
/**
|
||||
* Namespace for {@link CodingContract | coding contract} functions.
|
||||
* @remarks RAM cost: 0 GB
|
||||
*/
|
||||
readonly codingcontract: CodingContract;
|
||||
|
||||
/**
|
||||
* Namespace for {@link Cloud | cloud} functions.
|
||||
* @remarks RAM cost: 0 GB
|
||||
*/
|
||||
readonly cloud: Cloud;
|
||||
|
||||
/**
|
||||
* Namespace for darknet functions. Contains spoilers.
|
||||
* @remarks RAM cost: 0 GB
|
||||
*/
|
||||
readonly dnet: Darknet;
|
||||
|
||||
/**
|
||||
* Namespace for {@link Format | formatting} functions.
|
||||
* @remarks RAM cost: 0 GB
|
||||
*/
|
||||
readonly format: Format;
|
||||
|
||||
/**
|
||||
* Namespace for {@link Gang | gang} functions. Contains spoilers.
|
||||
* @remarks RAM cost: 0 GB
|
||||
*/
|
||||
readonly gang: Gang;
|
||||
|
||||
/**
|
||||
* Namespace for {@link Go | Go} functions.
|
||||
* @remarks RAM cost: 0 GB
|
||||
*/
|
||||
readonly go: Go;
|
||||
|
||||
/**
|
||||
* Namespace for {@link Sleeve | sleeve} functions. Contains spoilers.
|
||||
* @remarks RAM cost: 0 GB
|
||||
*/
|
||||
readonly sleeve: Sleeve;
|
||||
|
||||
/**
|
||||
* Namespace for {@link Stock | stock} functions.
|
||||
* @remarks RAM cost: 0 GB
|
||||
*/
|
||||
readonly stock: Stock;
|
||||
|
||||
/**
|
||||
* Namespace for {@link Formulas | formulas} functions.
|
||||
* @remarks RAM cost: 0 GB
|
||||
*/
|
||||
readonly formulas: Formulas;
|
||||
|
||||
/**
|
||||
* Namespace for {@link Stanek | Stanek} functions. Contains spoilers.
|
||||
* @remarks RAM cost: 0 GB
|
||||
*/
|
||||
readonly stanek: Stanek;
|
||||
|
||||
/**
|
||||
* Namespace for {@link Infiltration | infiltration} functions.
|
||||
* @remarks RAM cost: 0 GB
|
||||
*/
|
||||
readonly infiltration: Infiltration;
|
||||
|
||||
/**
|
||||
* Namespace for {@link Corporation | corporation} functions. Contains spoilers.
|
||||
* @remarks RAM cost: 0 GB
|
||||
*/
|
||||
readonly corporation: Corporation;
|
||||
|
||||
/**
|
||||
* Namespace for {@link UserInterface | user interface} functions.
|
||||
* @remarks RAM cost: 0 GB
|
||||
*/
|
||||
readonly ui: UserInterface;
|
||||
|
||||
/**
|
||||
* Namespace for {@link Singularity | singularity} functions. Contains spoilers.
|
||||
* @remarks RAM cost: 0 GB
|
||||
*/
|
||||
readonly singularity: Singularity;
|
||||
|
||||
/**
|
||||
* Namespace for {@link Grafting | grafting} functions. Contains spoilers.
|
||||
* @remarks RAM cost: 0 GB
|
||||
*/
|
||||
readonly grafting: Grafting;
|
||||
|
||||
/**
|
||||
* Arguments passed into the script.
|
||||
*
|
||||
* @remarks
|
||||
* RAM cost: 0 GB
|
||||
*
|
||||
* Arguments passed into a script can be accessed as a normal array by using the `[]` operator
|
||||
* 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.
|
||||
@@ -7905,6 +7910,11 @@ export interface NS {
|
||||
* // arguments to the script.
|
||||
* 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 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.
|
||||
@@ -8042,7 +8052,11 @@ export interface NS {
|
||||
* 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 destination - Hostname/IP of the destination server, which is the server to which the file will be copied.
|
||||
|
||||
@@ -18,7 +18,6 @@ import { OpenScript } from "./OpenScript";
|
||||
import { Tab } from "./Tab";
|
||||
import { SpecialServers } from "../../Server/data/SpecialServers";
|
||||
|
||||
const tabsMaxWidth = 1640;
|
||||
const searchWidth = 180;
|
||||
|
||||
interface IProps {
|
||||
@@ -101,7 +100,6 @@ export function Tabs({ scripts, currentScript, onTabClick, onTabClose, onTabUpda
|
||||
<Droppable droppableId="tabs" direction="horizontal">
|
||||
{(provided, snapshot) => (
|
||||
<Box
|
||||
maxWidth={`${tabsMaxWidth}px`}
|
||||
display="flex"
|
||||
flexGrow="1"
|
||||
flexDirection="row"
|
||||
|
||||
@@ -89,12 +89,7 @@ export const disconnectServers = (server1: BaseServer, server2: BaseServer) => {
|
||||
};
|
||||
|
||||
export function ipExists(ip: string): boolean {
|
||||
for (const server of AllServers.values()) {
|
||||
if (server.ip === ip) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return AllServers.has(ip);
|
||||
}
|
||||
|
||||
export function createUniqueRandomIp(): IPAddress {
|
||||
|
||||
@@ -26,6 +26,7 @@ import { Settings } from "../Settings/Settings";
|
||||
import type { ScriptKey } from "../utils/helpers/scriptKey";
|
||||
import { assertObject } from "../utils/TypeAssertion";
|
||||
import { clampNumber } from "../utils/helpers/clampNumber";
|
||||
import { roundToTwo } from "../utils/helpers/roundToTwo";
|
||||
|
||||
export interface BaseServerConstructorParams {
|
||||
adminRights?: boolean;
|
||||
@@ -233,7 +234,7 @@ export abstract class BaseServer implements IServer {
|
||||
}
|
||||
|
||||
updateRamUsed(ram: number): void {
|
||||
this.ramUsed = clampNumber(ram, 0, this.maxRam);
|
||||
this.ramUsed = roundToTwo(clampNumber(ram, 0, this.maxRam));
|
||||
}
|
||||
|
||||
pushProgram(program: ProgramFilePath | CompletedProgramName): void {
|
||||
|
||||
@@ -359,6 +359,7 @@ export function SidebarRoot(props: { page: Page }): React.ReactElement {
|
||||
canStaneksGift && { key_: Page.StaneksGift, icon: DeveloperBoardIcon },
|
||||
]}
|
||||
/>
|
||||
<Typography id="sidebar-extra-hook-0"></Typography>
|
||||
<Divider />
|
||||
<SidebarAccordion
|
||||
key_="Character"
|
||||
@@ -386,6 +387,7 @@ export function SidebarRoot(props: { page: Page }): React.ReactElement {
|
||||
canOpenGrafting && { key_: Page.Grafting, icon: BiotechIcon },
|
||||
]}
|
||||
/>
|
||||
<Typography id="sidebar-extra-hook-1"></Typography>
|
||||
<Divider />
|
||||
<SidebarAccordion
|
||||
key_="World"
|
||||
@@ -411,6 +413,7 @@ export function SidebarRoot(props: { page: Page }): React.ReactElement {
|
||||
canDarkNet && { key_: Page.DarkNet, icon: ShareIcon },
|
||||
]}
|
||||
/>
|
||||
<Typography id="sidebar-extra-hook-2"></Typography>
|
||||
<Divider />
|
||||
<SidebarAccordion
|
||||
key_="Help"
|
||||
@@ -428,6 +431,7 @@ export function SidebarRoot(props: { page: Page }): React.ReactElement {
|
||||
process.env.NODE_ENV === "development" && { key_: Page.DevMenu, icon: DeveloperBoardIcon },
|
||||
]}
|
||||
/>
|
||||
<Typography id="sidebar-extra-hook-3"></Typography>
|
||||
</List>
|
||||
</Drawer>
|
||||
);
|
||||
|
||||
@@ -5,7 +5,7 @@ export const TerminalHelpText: string[] = [
|
||||
" analyze Get information about the current machine ",
|
||||
" backdoor Install a backdoor on the current machine ",
|
||||
" 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",
|
||||
" changelog Display changelog",
|
||||
" check [script] [args...] Print a script's logs to Terminal",
|
||||
|
||||
@@ -78,6 +78,7 @@ import { commitHash } from "../utils/helpers/commitHash";
|
||||
import { apr1 } from "./commands/apr1";
|
||||
import { changelog } from "./commands/changelog";
|
||||
import { clear } from "./commands/clear";
|
||||
import { mkdir } from "./commands/mkdir";
|
||||
import { currentNodeMults } from "../BitNode/BitNodeMultipliers";
|
||||
import { Engine } from "../engine";
|
||||
import { Directory, resolveDirectory, root } from "../Paths/Directory";
|
||||
@@ -134,8 +135,12 @@ export const TerminalCommands: Record<string, (args: (string | number | boolean)
|
||||
vim: vim,
|
||||
weaken: weaken,
|
||||
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 {
|
||||
// Flags to determine whether the player is currently running a hack or an analyze
|
||||
action: TTimer | null = null;
|
||||
@@ -149,6 +154,8 @@ export class Terminal {
|
||||
|
||||
// True if a Coding Contract prompt is opened
|
||||
contractOpen = false;
|
||||
// True if a prompt is opened via the ns.prompt() API
|
||||
nsPromptApiOpen = false;
|
||||
|
||||
// Path of current directory
|
||||
currDir = "" as Directory;
|
||||
@@ -875,8 +882,7 @@ export class Terminal {
|
||||
}
|
||||
|
||||
function findSimilarCommands(command: string): string[] {
|
||||
const commands = Object.keys(TerminalCommands);
|
||||
const offByOneLetter = commands.filter((c) => {
|
||||
const offByOneLetter = supportedCommands.filter((c) => {
|
||||
if (c.length !== command.length) return false;
|
||||
let diff = 0;
|
||||
for (let i = 0; i < c.length; i++) {
|
||||
@@ -884,6 +890,6 @@ function findSimilarCommands(command: string): string[] {
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
8
src/Terminal/commands/mkdir.ts
Normal file
8
src/Terminal/commands/mkdir.ts
Normal 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,20 +11,27 @@ import libarg from "arg";
|
||||
import { getAllDirectories, resolveDirectory, root } from "../Paths/Directory";
|
||||
import { isLegacyScript, resolveScriptFilePath } from "../Paths/ScriptFilePath";
|
||||
import { enums } from "../NetscriptFunctions";
|
||||
import { TerminalCommands } from "./Terminal";
|
||||
import { supportedCommands } from "./Terminal";
|
||||
import { Terminal } from "../Terminal";
|
||||
import { parseUnknownError } from "../utils/ErrorHelper";
|
||||
import { DarknetServer } from "../Server/DarknetServer";
|
||||
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
|
||||
* @param terminalText The current full text entered in the terminal
|
||||
* @param baseDir The current working directory.
|
||||
* @returns Array of possible string replacements for the current text being autocompleted.
|
||||
*/
|
||||
export async function getTabCompletionPossibilities(terminalText: string, baseDir = root): Promise<string[]> {
|
||||
// Get the current command text
|
||||
const currentText = /[^ ]*$/.exec(terminalText)?.[0] ?? "";
|
||||
// Get the current command text, treating unclosed quotes as a single token
|
||||
const currentText = extractCurrentText(terminalText);
|
||||
// Remove the current text from the commands string
|
||||
const valueWithoutCurrent = terminalText.substring(0, terminalText.length - currentText.length);
|
||||
// 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 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 addServerNames = () =>
|
||||
addGeneric({
|
||||
|
||||
@@ -6,7 +6,7 @@ import { Paper, Popper, TextField, Typography } from "@mui/material";
|
||||
import { KEY } from "../../utils/KeyboardEventKey";
|
||||
import { Terminal } from "../../Terminal";
|
||||
import { Player } from "@player";
|
||||
import { getTabCompletionPossibilities } from "../getTabCompletionPossibilities";
|
||||
import { extractCurrentText, getTabCompletionPossibilities } from "../getTabCompletionPossibilities";
|
||||
import { Settings } from "../../Settings/Settings";
|
||||
import { longestCommonStart } from "../../utils/StringHelperFunctions";
|
||||
import { exceptionAlert } from "../../utils/helpers/exceptionAlert";
|
||||
@@ -209,7 +209,9 @@ export function TerminalInput(): React.ReactElement {
|
||||
// Catch all key inputs and redirect them to the terminal.
|
||||
useEffect(() => {
|
||||
function keyDown(this: Document, event: KeyboardEvent): void {
|
||||
if (Terminal.contractOpen) return;
|
||||
if (Terminal.contractOpen || Terminal.nsPromptApiOpen) {
|
||||
return;
|
||||
}
|
||||
if (Terminal.action !== null && event.key === KEY.C && event.ctrlKey) {
|
||||
Terminal.finishAction(true);
|
||||
return;
|
||||
@@ -217,12 +219,21 @@ export function TerminalInput(): React.ReactElement {
|
||||
const ref = terminalInput.current;
|
||||
if (event.ctrlKey || event.metaKey) return;
|
||||
if (event.key === KEY.C && (event.ctrlKey || event.metaKey)) return; // trying to copy
|
||||
|
||||
// Don't steal focus from other input elements
|
||||
const target = event.target;
|
||||
if (
|
||||
(target instanceof HTMLInputElement ||
|
||||
target instanceof HTMLTextAreaElement ||
|
||||
(target instanceof HTMLElement && target.isContentEditable)) &&
|
||||
target !== ref
|
||||
) {
|
||||
return;
|
||||
}
|
||||
if (ref) ref.focus();
|
||||
}
|
||||
document.addEventListener("keydown", keyDown);
|
||||
return () => document.removeEventListener("keydown", keyDown);
|
||||
});
|
||||
}, []);
|
||||
|
||||
async function onKeyDown(event: React.KeyboardEvent<HTMLInputElement | HTMLTextAreaElement>): Promise<void> {
|
||||
const ref = terminalInput.current;
|
||||
@@ -255,13 +266,16 @@ export function TerminalInput(): React.ReactElement {
|
||||
if (possibilities.length === 0) return;
|
||||
|
||||
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) {
|
||||
saveValue(value.replace(/[^ ]*$/, possibilities[0]) + " ");
|
||||
saveValue(value.replace(replacePattern, possibilities[0]) + " ");
|
||||
return;
|
||||
}
|
||||
// More than one possibility, check to see if there is a longer common string than currentText.
|
||||
const longestMatch = longestCommonStart(possibilities);
|
||||
saveValue(value.replace(/[^ ]*$/, longestMatch));
|
||||
saveValue(value.replace(replacePattern, longestMatch));
|
||||
setPossibilities(possibilities);
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user