Compare commits

..

50 Commits

Author SHA1 Message Date
Snarling
633da38301 RELEASE: 2.6.2 (#1454) 2024-07-03 19:14:45 -04:00
catloversg
3c29757827 CORPORATION: Fix wrong product price calculation (#1451) 2024-07-02 15:11:32 -07:00
catloversg
960fe5aa8b BUGFIX: Wrong success chance in ns.bladeburner.getActionEstimatedSuccessChance (#1450) 2024-07-02 15:07:56 -07:00
catloversg
922f0bfcc5 BITVERSE: Fix React warning (#1449) 2024-07-01 13:17:30 -07:00
catloversg
e66a8e319f MISC: Remove debug code (#1448) 2024-07-01 13:16:47 -07:00
catloversg
3fafa23f28 GANG: Fix ns.gang.getRecruitsAvailable (#1442) 2024-06-28 20:18:29 -07:00
catloversg
b1c1fc24a9 GRAFTING: Add new api for checking ongoing grafting (#1435) 2024-06-28 19:59:18 -07:00
Tom Prince
32eb6324fd MISC: Simplify graftingIntBonus calculation. (#1445)
The weight of the intelligence bonus is a multiplier to the percentage increase. So, rather than calculate it with a weight of 3 and then divide by 3, we can just calculate it with a weight of 1.
2024-06-28 19:52:36 -07:00
Sphyxis
61ec7dde80 API: Get Sleeve Success at BB tasks through existing commands (#1428) 2024-06-28 19:49:52 -07:00
David Walker
9c9a69f2e2 NETSCRIPT: Add ramOverride() function (#1346)
This adds a way to dynamically change the static RAM limit of a script,
which is also its current RAM usage. This makes it possible for scripts
to dynamically change their memory footprint, opening up new strategies
beyond current ram-dodging.

Calling functions still permanently increases the *dynamic* memory
limit; RAM-dodging is still the optimal strategy for avoiding RAM costs,
in that sense.

This also adds dynamicRamUsage to the info returned by
`getRunningScript`, to allow introspection on the currently needed ram.
2024-06-28 18:42:20 -07:00
David Walker
1c20a24079 MISC: Make spawn able to have 0 delay (#1333)
This eliminates a hole where spawn was unrelaible, because other scripts
could jump in and steal the RAM. It's not an API break, because 0 used
to be an invalid value.
2024-06-28 18:41:41 -07:00
David Walker
06d742a7f3 BUGFIX: Fix rounding issues due to ramOverride edge cases (#1339)
*All* RAM calculations must take place in units of hundredths-of-a-GB in
order for there not to be issues.

Also adds slightly more verbose logging when the dynamic RAM check
fails.
2024-06-28 17:58:17 -07:00
muesli4brekkies
357cc568e9 TERMINAL: Tweaks and bugfixes to grep (#1431) 2024-06-28 15:13:49 -07:00
catloversg
21e984bda6 DOCUMENTATION: Clarify logging API (#1444) 2024-06-28 02:37:04 -07:00
catloversg
f620ec889c MISC: Update BitNode info and documentation (#1436) 2024-06-28 02:11:50 -07:00
catloversg
f162faf60a INFILTRATION: Improve accuracy of slash game UI (#1422) 2024-06-28 02:09:18 -07:00
catloversg
031b8b9cbb UI: Remember last position of documentation pages (#1434) 2024-06-28 02:08:10 -07:00
Snarling
64933419d6 Changelog update 2024-06-27 12:37:03 -04:00
Snarling
b597746343 TRAVEL: Unify implementation for Player and Sleeves (and some followup for #1365) (#1439) 2024-06-26 20:46:50 -04:00
catloversg
abdf5f52cd INFILTRATION: Handle automated infiltration (#1414) 2024-06-26 11:19:52 -04:00
Snarling
dc057d05fc minor package-lock update
Fixes an audit issue on a dev dependency
2024-06-25 19:30:30 -04:00
catloversg
cef789eb7c ELECTRON: Fix error with symbolic link (#1427) 2024-06-25 19:09:22 -04:00
catloversg
fd8eae5cf5 MISC: Cancel spawned scripts in Bitverse (#1429) 2024-06-24 22:20:08 -07:00
ilkecan
bf8c15332e CORPORATION: Don't check access for getConstants (#1430) 2024-06-24 22:18:57 -07:00
John Gietzen
819f877370 Update codingcontracttypes.ts to clarify '0' (#1433)
Fixes #1432
2024-06-24 22:02:11 -07:00
JamesWilcox-git
67cdd57728 BUGFIX: Hacknet level base cost multiplier fixed (#1412) 2024-06-24 21:40:50 -07:00
catloversg
5a8f0e99af SINGULARITY: Add ns.singularity.getFactionWorkTypes (#1425) 2024-06-24 20:43:36 -07:00
catloversg
0d8cc54c99 INFILTRATION: Fix React warnings (#1423) 2024-06-24 20:37:57 -07:00
catloversg
c0036b03d4 SINGULARITY: Allow being hospitalized while being busy (#1426) 2024-06-24 20:36:03 -07:00
catloversg
48bebeea2b MISC: Remove unused properties of source file (#1424) 2024-06-24 20:34:56 -07:00
Michael Ficocelli
49668f10b2 IPVGO: Fix displayed mult to match the actual bonus of SF 14.1 (#1419) 2024-06-24 20:31:09 -07:00
catloversg
847d45f4f4 GANG: Show equipments when there is not enough money (#1417) 2024-06-21 03:14:48 -07:00
catloversg
a62bdcafef BUGFIX: Fix a bug in ns.singularity.getAugmentationFactions (#1418) 2024-06-21 03:06:57 -07:00
catloversg
337fa4e274 GANG: Fix wrong wanted gain rate (#1415) 2024-06-21 03:05:33 -07:00
catloversg
eff834bfe9 BUGFIX: Fix wrong augmentation price (#1416) 2024-06-21 03:01:57 -07:00
David Walker
99b22a221c BUGFIX: Fix issues and edge-cases with rm (#1404) 2024-06-16 18:27:46 -07:00
catloversg
4382f860db CCT: Clarify empty string solution in UI (#1400) 2024-06-15 16:37:14 -07:00
catloversg
7a39a93fa9 MISC: Remove mention of re-sleeving (#1399) 2024-06-15 16:36:32 -07:00
catloversg
ceb58bc6b3 UI: Show BitNode multipliers in BN 5.1 (#1398) 2024-06-15 16:35:32 -07:00
Jesse Clark
f6de21ea18 UI: Clean up Hacknet UI (#1397) 2024-06-14 00:08:10 -07:00
muesli4brekkies
4936d14639 TERMINAL: Add grep command (#1381) 2024-06-14 00:00:48 -07:00
catloversg
a780880531 MISC: Add ns.enums to AutocompleteData (#1389) 2024-06-13 23:56:27 -07:00
Albert Llop
e9347fca76 DOCS: Fix example signature in IPvGO guide (#1396) 2024-06-13 23:55:46 -07:00
catloversg
417d420793 BLADEBURNER: Fix NaN in getSuccessRange (#1394) 2024-06-13 23:48:50 -07:00
catloversg
a12056a898 BLADEBURNER: Add remaining time for actions (#1391) 2024-06-12 19:21:23 -07:00
G4mingJon4s
bec6e82d7f EDITOR: changed editor tabs to have their own editor mode (#1372) 2024-06-12 19:19:40 -07:00
G4mingJon4s
805ca06922 TERMINAL: Added deleting entire directories using rm (#1378) 2024-06-12 19:17:39 -07:00
catloversg
344054f10d DOCUMENTATION: Clarify maximum number of ports (#1388) 2024-06-12 19:07:05 -07:00
catloversg
39b18e7659 BUGFIX: Fix wrong position name in ns.formulas.work.companyGains (#1393) 2024-06-12 14:34:13 -07:00
catloversg
2198a02152 MISC: Fix support of react-refresh-webpack-plugin (#1392) 2024-06-12 14:31:13 -07:00
151 changed files with 2178 additions and 3404 deletions

30
electron/fileError.html Normal file
View File

@@ -0,0 +1,30 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Bitburner</title>
<style>
body {
background-color: black;
color: #0c0;
margin: 0;
}
div {
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
}
h1 {
text-align: center;
}
</style>
</head>
<body>
<div>
<h1>Attempts to access local files outside the normal game environment will be directed to this file.</h1>
</div>
</body>
</html>

View File

@@ -1 +0,0 @@
Attempts to access local files outside the normal game environment will be directed to this file.

View File

@@ -29,6 +29,7 @@ const debounce = require("lodash/debounce");
const Store = require("electron-store");
const store = new Store();
const path = require("path");
const { realpathSync } = require("fs");
const { fileURLToPath } = require("url");
log.transports.file.level = store.get("file-log-level", "info");
@@ -201,13 +202,18 @@ app.on("ready", async () => {
// Intercept file protocol requests and only let valid requests through
protocol.interceptFileProtocol("file", ({ url, method }, callback) => {
const filePath = fileURLToPath(url);
const relativePath = path.relative(__dirname, filePath);
//only provide html files in same directory, or anything in dist
if ((method === "GET" && relativePath.startsWith("dist")) || relativePath.match(/^[a-zA-Z-_]*\.html/)) {
return callback(filePath);
const realPath = realpathSync(filePath);
const relativePath = path.relative(__dirname, realPath);
// Only allow access to files in "dist" folder or html files in the same directory
if (method === "GET" && (relativePath.startsWith("dist") || relativePath.match(/^[a-zA-Z-_]*\.html/))) {
callback(realPath);
return;
}
log.error(`Tried to access a page outside the sandbox. Url: ${url}. Method: ${method}.`);
callback(path.join(__dirname, "fileError.txt"));
log.error(
`Tried to access a page outside the sandbox. Url: ${url}. FilePath: ${filePath}. RealPath: ${realPath}.` +
` __dirname: ${__dirname}. RelativePath: ${relativePath}. Method: ${method}.`,
);
callback(path.join(__dirname, "fileError.html"));
});
log.info("Application is ready!");

View File

@@ -192,9 +192,9 @@ async function pushSaveDataToSteamCloud(saveData, currentPlayerId) {
*
* Instead of implementing it, the old code (encoding in base64) is used here for backward compatibility.
*/
const content = saveData.toString("base64");
log.debug(`Uncompressed: ${saveData.length} bytes`);
log.debug(`Compressed: ${content.length} bytes`);
const content = Buffer.from(saveData).toString("base64");
log.debug(`saveData: ${saveData.length} bytes`);
log.debug(`Base64 string of saveData: ${content.length} bytes`);
log.debug(`Saving to Steam Cloud as ${steamSaveName}`);
try {

View File

@@ -0,0 +1,11 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [bitburner](./bitburner.md) &gt; [AutocompleteData](./bitburner.autocompletedata.md) &gt; [enums](./bitburner.autocompletedata.enums.md)
## AutocompleteData.enums property
**Signature:**
```typescript
enums: NSEnums;
```

View File

@@ -16,6 +16,7 @@ interface AutocompleteData
| Property | Modifiers | Type | Description |
| --- | --- | --- | --- |
| [enums](./bitburner.autocompletedata.enums.md) | | [NSEnums](./bitburner.nsenums.md) | |
| [scripts](./bitburner.autocompletedata.scripts.md) | | string\[\] | |
| [servers](./bitburner.autocompletedata.servers.md) | | string\[\] | |
| [txts](./bitburner.autocompletedata.txts.md) | | string\[\] | |

View File

@@ -9,7 +9,7 @@ Get estimate success chance of an action.
**Signature:**
```typescript
getActionEstimatedSuccessChance(type: string, name: string): [number, number];
getActionEstimatedSuccessChance(type: string, name: string, sleeveNumber?: number): [number, number];
```
## Parameters
@@ -18,6 +18,7 @@ getActionEstimatedSuccessChance(type: string, name: string): [number, number];
| --- | --- | --- |
| type | string | Type of action. |
| name | string | Name of action. Must be an exact match. |
| sleeveNumber | number | _(Optional)_ Optional. Index of the sleeve to retrieve information. |
**Returns:**

View File

@@ -24,7 +24,7 @@ You have to be employed in the Bladeburner division and be in BitNode-7 or have
| [getActionCountRemaining(type, name)](./bitburner.bladeburner.getactioncountremaining.md) | Get action count remaining. |
| [getActionCurrentLevel(type, name)](./bitburner.bladeburner.getactioncurrentlevel.md) | Get the current level of an action. |
| [getActionCurrentTime()](./bitburner.bladeburner.getactioncurrenttime.md) | Get the time elapsed on current action. |
| [getActionEstimatedSuccessChance(type, name)](./bitburner.bladeburner.getactionestimatedsuccesschance.md) | Get estimate success chance of an action. |
| [getActionEstimatedSuccessChance(type, name, sleeveNumber)](./bitburner.bladeburner.getactionestimatedsuccesschance.md) | Get estimate success chance of an action. |
| [getActionMaxLevel(type, name)](./bitburner.bladeburner.getactionmaxlevel.md) | Get the maximum level of an action. |
| [getActionRepGain(type, name, level)](./bitburner.bladeburner.getactionrepgain.md) | Get the reputation gain of an action. |
| [getActionSuccesses(type, name)](./bitburner.bladeburner.getactionsuccesses.md) | Get action successes. |

View File

@@ -4,7 +4,7 @@
## Grafting.getAugmentationGraftTime() method
Retrieves the time required to graft an aug.
Retrieves the time required to graft an aug. Do not use this value to determine when the ongoing grafting finishes. The ongoing grafting is affected by current intelligence level and focus bonus. You should use [waitForOngoingGrafting](./bitburner.grafting.waitforongoinggrafting.md) for that purpose.
**Signature:**
@@ -22,7 +22,7 @@ getAugmentationGraftTime(augName: string): number;
number
The time required, in millis, to graft the named augmentation.
The time required, in milliseconds, to graft the named augmentation.
## Exceptions

View File

@@ -4,7 +4,7 @@
## Grafting.graftAugmentation() method
Begins grafting the named aug. You must be in New Tokyo to use this.
Begins grafting the named aug. You must be in New Tokyo to use this. When you call this API, the current work (grafting or other actions) will be canceled.
**Signature:**

View File

@@ -21,7 +21,8 @@ This API requires Source-File 10 to use.
| Method | Description |
| --- | --- |
| [getAugmentationGraftPrice(augName)](./bitburner.grafting.getaugmentationgraftprice.md) | Retrieve the grafting cost of an aug. |
| [getAugmentationGraftTime(augName)](./bitburner.grafting.getaugmentationgrafttime.md) | Retrieves the time required to graft an aug. |
| [getAugmentationGraftTime(augName)](./bitburner.grafting.getaugmentationgrafttime.md) | Retrieves the time required to graft an aug. Do not use this value to determine when the ongoing grafting finishes. The ongoing grafting is affected by current intelligence level and focus bonus. You should use [waitForOngoingGrafting](./bitburner.grafting.waitforongoinggrafting.md) for that purpose. |
| [getGraftableAugmentations()](./bitburner.grafting.getgraftableaugmentations.md) | Retrieves a list of Augmentations that can be grafted. |
| [graftAugmentation(augName, focus)](./bitburner.grafting.graftaugmentation.md) | Begins grafting the named aug. You must be in New Tokyo to use this. |
| [graftAugmentation(augName, focus)](./bitburner.grafting.graftaugmentation.md) | Begins grafting the named aug. You must be in New Tokyo to use this. When you call this API, the current work (grafting or other actions) will be canceled. |
| [waitForOngoingGrafting()](./bitburner.grafting.waitforongoinggrafting.md) | Wait until the ongoing grafting finishes or is canceled. |

View File

@@ -0,0 +1,23 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [bitburner](./bitburner.md) &gt; [Grafting](./bitburner.grafting.md) &gt; [waitForOngoingGrafting](./bitburner.grafting.waitforongoinggrafting.md)
## Grafting.waitForOngoingGrafting() method
Wait until the ongoing grafting finishes or is canceled.
**Signature:**
```typescript
waitForOngoingGrafting(): Promise<void>;
```
**Returns:**
Promise&lt;void&gt;
A promise that resolves when the current grafting finishes or is canceled. If there is no current work, the promise resolves immediately. If the current work is not a grafting work, the promise rejects immediately.
## Remarks
RAM cost: 1 GB

View File

@@ -0,0 +1,11 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [bitburner](./bitburner.md) &gt; [GraftingTask](./bitburner.graftingtask.md) &gt; [completion](./bitburner.graftingtask.completion.md)
## GraftingTask.completion property
**Signature:**
```typescript
completion: Promise<void>;
```

View File

@@ -21,6 +21,7 @@ An object representing the current grafting status
| Property | Modifiers | Type | Description |
| --- | --- | --- | --- |
| [augmentation](./bitburner.graftingtask.augmentation.md) | | string | |
| [completion](./bitburner.graftingtask.completion.md) | | Promise&lt;void&gt; | |
| [cyclesWorked](./bitburner.graftingtask.cyclesworked.md) | | number | |
| [type](./bitburner.graftingtask.type.md) | | "GRAFTING" | |

View File

@@ -4,7 +4,7 @@
## NS.disableLog() method
Disables logging for the given function.
Disables logging for the given NS function.
**Signature:**
@@ -16,7 +16,7 @@ disableLog(fn: string): void;
| Parameter | Type | Description |
| --- | --- | --- |
| fn | string | Name of function for which to disable logging. |
| fn | string | Name of the NS function for which to disable logging. |
**Returns:**
@@ -30,3 +30,11 @@ Logging can be disabled for all functions by passing `ALL` as the argument.
For specific interfaces, use the form "namespace.functionName". (e.g. "ui.setTheme")
## Example
```js
ns.disableLog("hack"); // Disable logging for `ns.hack()`
```

View File

@@ -4,7 +4,7 @@
## NS.enableLog() method
Enable logging for a certain function.
Enables logging for the given NS function.
**Signature:**
@@ -16,7 +16,7 @@ enableLog(fn: string): void;
| Parameter | Type | Description |
| --- | --- | --- |
| fn | string | Name of function for which to enable logging. |
| fn | string | Name of the NS function for which to enable logging. |
**Returns:**
@@ -26,5 +26,13 @@ void
RAM cost: 0 GB
Re-enables logging for the given function. If `ALL` is passed into this function as an argument, then it will revert the effects of disableLog(`ALL`<!-- -->).
Re-enables logging for the given function. If `ALL` is passed into this function as an argument, it will revert the effect of disableLog("ALL").
## Example
```js
ns.enableLog("hack"); // Enable logging for `ns.hack()`
```

View File

@@ -4,7 +4,7 @@
## NS.isLogEnabled() method
Checks the status of the logging for the given function.
Checks the status of the logging for the given NS function.
**Signature:**
@@ -22,9 +22,17 @@ isLogEnabled(fn: string): boolean;
boolean
Returns a boolean indicating whether or not logging is enabled for that function (or `ALL`<!-- -->).
Returns a boolean indicating whether or not logging is enabled for that NS function (or `ALL`<!-- -->).
## Remarks
RAM cost: 0 GB
## Example
```js
ns.print(ns.isLogEnabled("hack")); // Check if logging is enabled for `ns.hack()`
```

View File

@@ -63,8 +63,8 @@ export async function main(ns) {
| [clearPort(portNumber)](./bitburner.ns.clearport.md) | Clear data from a port. |
| [closeTail(pid)](./bitburner.ns.closetail.md) | Close the tail window of a script. |
| [deleteServer(host)](./bitburner.ns.deleteserver.md) | Delete a purchased server. |
| [disableLog(fn)](./bitburner.ns.disablelog.md) | Disables logging for the given function. |
| [enableLog(fn)](./bitburner.ns.enablelog.md) | Enable logging for a certain function. |
| [disableLog(fn)](./bitburner.ns.disablelog.md) | Disables logging for the given NS function. |
| [enableLog(fn)](./bitburner.ns.enablelog.md) | Enables logging for the given NS function. |
| [exec(script, hostname, threadOrOptions, args)](./bitburner.ns.exec.md) | Start another script on any server. |
| [exit()](./bitburner.ns.exit.md) | Terminates the current script immediately. |
| [fileExists(filename, host)](./bitburner.ns.fileexists.md) | Check if a file exists. |
@@ -125,7 +125,7 @@ export async function main(ns) {
| [hasRootAccess(host)](./bitburner.ns.hasrootaccess.md) | Check if you have root access on a server. |
| [hasTorRouter()](./bitburner.ns.hastorrouter.md) | Returns whether the player has access to the darkweb. |
| [httpworm(host)](./bitburner.ns.httpworm.md) | Runs HTTPWorm.exe on a server. |
| [isLogEnabled(fn)](./bitburner.ns.islogenabled.md) | Checks the status of the logging for the given function. |
| [isLogEnabled(fn)](./bitburner.ns.islogenabled.md) | Checks the status of the logging for the given NS function. |
| [isRunning(script, host, args)](./bitburner.ns.isrunning.md) | Check if a script is running. |
| [kill(pid)](./bitburner.ns.kill.md) | Terminate the script with the provided PID. |
| [kill(filename, hostname, args)](./bitburner.ns.kill_1.md) | Terminate the script(s) with the provided filename, hostname, and script arguments. |
@@ -143,6 +143,7 @@ export async function main(ns) {
| [prompt(txt, options)](./bitburner.ns.prompt.md) | Prompt the player with an input modal. |
| [ps(host)](./bitburner.ns.ps.md) | List running scripts on a server. |
| [purchaseServer(hostname, ram)](./bitburner.ns.purchaseserver.md) | Purchase a server. |
| [ramOverride(ram)](./bitburner.ns.ramoverride.md) | Change the current static RAM allocation of the script. |
| [read(filename)](./bitburner.ns.read.md) | Read content of a file. |
| [readPort(portNumber)](./bitburner.ns.readport.md) | Read data from a port. |
| [relaysmtp(host)](./bitburner.ns.relaysmtp.md) | Runs relaySMTP.exe on a server. |

View File

@@ -0,0 +1,34 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [bitburner](./bitburner.md) &gt; [NS](./bitburner.ns.md) &gt; [ramOverride](./bitburner.ns.ramoverride.md)
## NS.ramOverride() method
Change the current static RAM allocation of the script.
**Signature:**
```typescript
ramOverride(ram?: number): number;
```
## Parameters
| Parameter | Type | Description |
| --- | --- | --- |
| ram | number | _(Optional)_ The new RAM limit to set. |
**Returns:**
number
The new static RAM limit, which will be the old one if it wasn't changed. This means you can use no parameters to check the current ram limit.
## Remarks
RAM cost: 0 GB
This acts analagously to the ramOverride parameter in runOptions, but for changing RAM in the current running script. The static RAM allocation (the amount of RAM used by ONE thread) will be adjusted to the given value, if possible. This can fail if the number is less than the current dynamic RAM limit, or if adjusting upward would require more RAM than is available on the server.
RAM usage will be rounded to the nearest hundredth of a GB, which is the granularity of all RAM calculations.

View File

@@ -30,6 +30,8 @@ RAM cost: 2 GB
Terminates the current script, and then after a defined delay it will execute the newly-specified script. The purpose of this function is to execute a new script without being constrained by the RAM usage of the current one. This function can only be used to run scripts on the local server.
The delay specified can be 0; in this case the new script will synchronously replace the old one. (There will not be any opportunity for other scripts to use up the RAM in-between.)
Because this function immediately terminates the script, it does not have a return value.
Running this function with 0 or fewer threads will cause a runtime error.

View File

@@ -31,3 +31,5 @@ RAM cost: 0 GB
Write data to the given Netscript port.
There is a limit on the maximum number of ports, but you won't reach that limit in normal situations. If you do, it usually means that there is a bug in your script that leaks port data. A port is freed when it does not have any data in its underlying queue. `ns.clearPort` deletes all data on a port. `ns.readPort` reads the first element in the port's queue, then removes it from the queue.

View File

@@ -0,0 +1,15 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [bitburner](./bitburner.md) &gt; [RunningScript](./bitburner.runningscript.md) &gt; [dynamicRamUsage](./bitburner.runningscript.dynamicramusage.md)
## RunningScript.dynamicRamUsage property
The dynamic RAM usage of (one thread of) this script instance. Does not affect overall RAM consumption (ramUsage is for that), but rather shows how much of the reserved RAM is currently in use via all the ns functions the script has called. Initially 1.6GB, this increases as new functions are called.
Only set for scripts that are still running.
**Signature:**
```typescript
dynamicRamUsage: number | undefined;
```

View File

@@ -16,6 +16,7 @@ interface RunningScript
| Property | Modifiers | Type | Description |
| --- | --- | --- | --- |
| [args](./bitburner.runningscript.args.md) | | [ScriptArg](./bitburner.scriptarg.md)<!-- -->\[\] | Arguments the script was called with |
| [dynamicRamUsage](./bitburner.runningscript.dynamicramusage.md) | | number \| undefined | <p>The dynamic RAM usage of (one thread of) this script instance. Does not affect overall RAM consumption (ramUsage is for that), but rather shows how much of the reserved RAM is currently in use via all the ns functions the script has called. Initially 1.6GB, this increases as new functions are called.</p><p>Only set for scripts that are still running.</p> |
| [filename](./bitburner.runningscript.filename.md) | | string | Filename of the script |
| [logs](./bitburner.runningscript.logs.md) | | string\[\] | Script logs as an array. The newest log entries are at the bottom. Timestamps, if enabled, are placed inside <code>[brackets]</code> at the start of each line. |
| [offlineExpGained](./bitburner.runningscript.offlineexpgained.md) | | number | Total amount of hacking experience earned from this script when offline |
@@ -25,7 +26,7 @@ interface RunningScript
| [onlineMoneyMade](./bitburner.runningscript.onlinemoneymade.md) | | number | Total amount of money made by this script when online |
| [onlineRunningTime](./bitburner.runningscript.onlinerunningtime.md) | | number | Number of seconds that this script has been running online |
| [pid](./bitburner.runningscript.pid.md) | | number | Process ID. Must be an integer |
| [ramUsage](./bitburner.runningscript.ramusage.md) | | number | How much RAM this script uses for ONE thread |
| [ramUsage](./bitburner.runningscript.ramusage.md) | | number | How much RAM this script uses for ONE thread. Also known as "static RAM usage," this value does not change once the script is started, unless you call ns.ramOverride(). |
| [server](./bitburner.runningscript.server.md) | | string | Hostname of the server on which this script runs |
| [tailProperties](./bitburner.runningscript.tailproperties.md) | | [TailProperties](./bitburner.tailproperties.md) \| null | Properties of the tail window, or null if it is not shown |
| [temporary](./bitburner.runningscript.temporary.md) | | boolean | Whether this RunningScript is excluded from saves |

View File

@@ -4,7 +4,7 @@
## RunningScript.ramUsage property
How much RAM this script uses for ONE thread
How much RAM this script uses for ONE thread. Also known as "static RAM usage," this value does not change once the script is started, unless you call ns.ramOverride().
**Signature:**

View File

@@ -16,7 +16,7 @@ interface RunOptions
| Property | Modifiers | Type | Description |
| --- | --- | --- | --- |
| [preventDuplicates?](./bitburner.runoptions.preventduplicates.md) | | boolean | _(Optional)_ Should we fail to run if another instance is running with the exact same arguments? This used to be the default behavior, now defaults to false. |
| [ramOverride?](./bitburner.runoptions.ramoverride.md) | | number | <p>_(Optional)_ The RAM allocation to launch each thread of the script with.</p><p>Lowering this will <i>not</i> automatically let you get away with using less RAM: the dynamic RAM check enforces that all [NS](./bitburner.ns.md) functions actually called incur their cost. However, if you know that certain functions that are statically present (and thus included in the static RAM cost) will never be called in a particular circumstance, you can use this to avoid paying for them.</p><p>You can also use this to <i>increase</i> the RAM if the static RAM checker has missed functions that you need to call.</p><p>Must be greater-or-equal to the base RAM cost. Defaults to the statically calculated cost.</p> |
| [ramOverride?](./bitburner.runoptions.ramoverride.md) | | number | <p>_(Optional)_ The RAM allocation to launch each thread of the script with.</p><p>Lowering this will <i>not</i> automatically let you get away with using less RAM: the dynamic RAM check enforces that all [NS](./bitburner.ns.md) functions actually called incur their cost. However, if you know that certain functions that are statically present (and thus included in the static RAM cost) will never be called in a particular circumstance, you can use this to avoid paying for them.</p><p>You can also use this to <i>increase</i> the RAM if the static RAM checker has missed functions that you need to call.</p><p>Must be greater-or-equal to the base RAM cost. Will be rounded to the nearest hundredth-of-a-GB, which is the granularity of all RAM calculations. Defaults to the statically calculated cost.</p> |
| [temporary?](./bitburner.runoptions.temporary.md) | | boolean | _(Optional)_ Whether this script is excluded from saves, defaults to false |
| [threads?](./bitburner.runoptions.threads.md) | | number | _(Optional)_ Number of threads that the script will run with, defaults to 1 |

View File

@@ -10,7 +10,7 @@ Lowering this will <i>not</i> automatically let you get away with using less RAM
You can also use this to <i>increase</i> the RAM if the static RAM checker has missed functions that you need to call.
Must be greater-or-equal to the base RAM cost. Defaults to the statically calculated cost.
Must be greater-or-equal to the base RAM cost. Will be rounded to the nearest hundredth-of-a-GB, which is the granularity of all RAM calculations. Defaults to the statically calculated cost.
**Signature:**

View File

@@ -0,0 +1,32 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [bitburner](./bitburner.md) &gt; [Singularity](./bitburner.singularity.md) &gt; [getFactionWorkTypes](./bitburner.singularity.getfactionworktypes.md)
## Singularity.getFactionWorkTypes() method
Get the work types of a faction.
**Signature:**
```typescript
getFactionWorkTypes(faction: string): FactionWorkType[];
```
## Parameters
| Parameter | Type | Description |
| --- | --- | --- |
| faction | string | Name of the faction. |
**Returns:**
[FactionWorkType](./bitburner.factionworktype.md)<!-- -->\[\]
The work types of the faction.
## Remarks
RAM cost: 1 GB \* 16/4/1
This function returns an array containing the work types of the specified faction.

View File

@@ -53,6 +53,7 @@ This API requires Source-File 4 to use. The RAM cost of all these functions is m
| [getFactionFavorGain(faction)](./bitburner.singularity.getfactionfavorgain.md) | Get faction favor gain. |
| [getFactionInviteRequirements(faction)](./bitburner.singularity.getfactioninviterequirements.md) | List conditions for being invited to a faction. |
| [getFactionRep(faction)](./bitburner.singularity.getfactionrep.md) | Get faction reputation. |
| [getFactionWorkTypes(faction)](./bitburner.singularity.getfactionworktypes.md) | Get the work types of a faction. |
| [getOwnedAugmentations(purchased)](./bitburner.singularity.getownedaugmentations.md) | Get a list of owned augmentation. |
| [getOwnedSourceFiles()](./bitburner.singularity.getownedsourcefiles.md) | Get a list of acquired Source-Files. |
| [getUpgradeHomeCoresCost()](./bitburner.singularity.getupgradehomecorescost.md) | Get the price of upgrading home cores. |

View File

@@ -16,5 +16,5 @@ interface SpawnOptions extends RunOptions
| Property | Modifiers | Type | Description |
| --- | --- | --- | --- |
| [spawnDelay?](./bitburner.spawnoptions.spawndelay.md) | | number | _(Optional)_ Number of milliseconds to delay before spawning script, defaults to 10000 (10s). Must be a positive integer. |
| [spawnDelay?](./bitburner.spawnoptions.spawndelay.md) | | number | _(Optional)_ Number of milliseconds to delay before spawning script, defaults to 10000 (10s). Must be a non-negative integer. If 0, the script will be spawned synchronously. |

View File

@@ -4,7 +4,7 @@
## SpawnOptions.spawnDelay property
Number of milliseconds to delay before spawning script, defaults to 10000 (10s). Must be a positive integer.
Number of milliseconds to delay before spawning script, defaults to 10000 (10s). Must be a non-negative integer. If 0, the script will be spawned synchronously.
**Signature:**

99
package-lock.json generated
View File

@@ -5946,13 +5946,14 @@
"dev": true
},
"node_modules/body-parser": {
"version": "1.20.1",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz",
"integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==",
"version": "1.20.2",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz",
"integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==",
"dev": true,
"license": "MIT",
"dependencies": {
"bytes": "3.1.2",
"content-type": "~1.0.4",
"content-type": "~1.0.5",
"debug": "2.6.9",
"depd": "2.0.0",
"destroy": "1.2.0",
@@ -5960,7 +5961,7 @@
"iconv-lite": "0.4.24",
"on-finished": "2.4.1",
"qs": "6.11.0",
"raw-body": "2.5.1",
"raw-body": "2.5.2",
"type-is": "~1.6.18",
"unpipe": "1.0.0"
},
@@ -5974,6 +5975,7 @@
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
"integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
@@ -5983,6 +5985,7 @@
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"dev": true,
"license": "MIT",
"dependencies": {
"ms": "2.0.0"
}
@@ -5991,7 +5994,8 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
"dev": true
"dev": true,
"license": "MIT"
},
"node_modules/bonjour-service": {
"version": "1.1.1",
@@ -6029,12 +6033,13 @@
}
},
"node_modules/braces": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
"dev": true,
"license": "MIT",
"dependencies": {
"fill-range": "^7.0.1"
"fill-range": "^7.1.1"
},
"engines": {
"node": ">=8"
@@ -6599,6 +6604,7 @@
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
"integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
@@ -6610,10 +6616,11 @@
"dev": true
},
"node_modules/cookie": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz",
"integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==",
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",
"integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
@@ -8233,17 +8240,18 @@
}
},
"node_modules/express": {
"version": "4.18.2",
"resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz",
"integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==",
"version": "4.19.2",
"resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz",
"integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"accepts": "~1.3.8",
"array-flatten": "1.1.1",
"body-parser": "1.20.1",
"body-parser": "1.20.2",
"content-disposition": "0.5.4",
"content-type": "~1.0.4",
"cookie": "0.5.0",
"cookie": "0.6.0",
"cookie-signature": "1.0.6",
"debug": "2.6.9",
"depd": "2.0.0",
@@ -8485,10 +8493,11 @@
}
},
"node_modules/fill-range": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
"dev": true,
"license": "MIT",
"dependencies": {
"to-regex-range": "^5.0.1"
},
@@ -8635,9 +8644,9 @@
}
},
"node_modules/follow-redirects": {
"version": "1.15.3",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz",
"integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==",
"version": "1.15.6",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz",
"integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==",
"dev": true,
"funding": [
{
@@ -8645,6 +8654,7 @@
"url": "https://github.com/sponsors/RubenVerborgh"
}
],
"license": "MIT",
"engines": {
"node": ">=4.0"
},
@@ -8901,20 +8911,6 @@
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
"dev": true
},
"node_modules/fsevents": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
"dev": true,
"hasInstallScript": true,
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/function-bind": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
@@ -9811,6 +9807,7 @@
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
"dev": true,
"license": "MIT",
"dependencies": {
"safer-buffer": ">= 2.1.2 < 3"
},
@@ -10306,6 +10303,7 @@
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.12.0"
}
@@ -12503,14 +12501,15 @@
}
},
"node_modules/katex": {
"version": "0.16.9",
"resolved": "https://registry.npmjs.org/katex/-/katex-0.16.9.tgz",
"integrity": "sha512-fsSYjWS0EEOwvy81j3vRA8TEAhQhKiqO+FQaKWp0m39qwOzHVBgAUBIXWj1pB+O2W3fIpNa6Y9KSKCVbfPhyAQ==",
"version": "0.16.10",
"resolved": "https://registry.npmjs.org/katex/-/katex-0.16.10.tgz",
"integrity": "sha512-ZiqaC04tp2O5utMsl2TEZTXxa6WSC4yo0fv5ML++D3QZv/vx2Mct0mTlRx3O+uUkjfuAgOkzsCmq5MiUEsDDdA==",
"dev": true,
"funding": [
"https://opencollective.com/katex",
"https://github.com/sponsors/katex"
],
"license": "MIT",
"dependencies": {
"commander": "^8.3.0"
},
@@ -13127,6 +13126,7 @@
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
"integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
@@ -14926,6 +14926,7 @@
"resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz",
"integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==",
"dev": true,
"license": "BSD-3-Clause",
"dependencies": {
"side-channel": "^1.0.4"
},
@@ -14998,10 +14999,11 @@
}
},
"node_modules/raw-body": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz",
"integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==",
"version": "2.5.2",
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz",
"integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==",
"dev": true,
"license": "MIT",
"dependencies": {
"bytes": "3.1.2",
"http-errors": "2.0.0",
@@ -15017,6 +15019,7 @@
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
"integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
@@ -16788,6 +16791,7 @@
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"is-number": "^7.0.0"
},
@@ -16959,6 +16963,7 @@
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
"integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
"dev": true,
"license": "MIT",
"dependencies": {
"media-typer": "0.3.0",
"mime-types": "~2.1.24"
@@ -18023,9 +18028,9 @@
}
},
"node_modules/ws": {
"version": "8.14.2",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.14.2.tgz",
"integrity": "sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==",
"version": "8.17.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
"integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
"dev": true,
"engines": {
"node": ">=10.0.0"

View File

@@ -13,11 +13,26 @@ import { Page } from "../ui/Router";
import { mergeMultipliers } from "../PersonObjects/Multipliers";
import { currentNodeMults } from "../BitNode/BitNodeMultipliers";
const soaAugmentationNames = [
AugmentationName.BeautyOfAphrodite,
AugmentationName.ChaosOfDionysus,
AugmentationName.FloodOfPoseidon,
AugmentationName.HuntOfArtemis,
AugmentationName.KnowledgeOfApollo,
AugmentationName.MightOfAres,
AugmentationName.TrickeryOfHermes,
AugmentationName.WKSharmonizer,
AugmentationName.WisdomOfAthena,
];
export function getBaseAugmentationPriceMultiplier(): number {
return CONSTANTS.MultipleAugMultiplier * [1, 0.96, 0.94, 0.93][Player.sourceFileLvl(11)];
}
export function getGenericAugmentationPriceMultiplier(): number {
return Math.pow(getBaseAugmentationPriceMultiplier(), Player.queuedAugmentations.length);
const queuedNonSoAAugmentationList = Player.queuedAugmentations.filter((augmentation) => {
return !soaAugmentationNames.includes(augmentation.name);
});
return Math.pow(getBaseAugmentationPriceMultiplier(), queuedNonSoAAugmentationList.length);
}
export function applyAugmentation(aug: PlayerOwnedAugmentation, reapply = false): void {
@@ -111,7 +126,7 @@ export function getAugCost(aug: Augmentation): AugmentationCosts {
const multiplier = Math.pow(CONSTANTS.NeuroFluxGovernorLevelMult, aug.getLevel());
repCost = aug.baseRepRequirement * multiplier * currentNodeMults.AugmentationRepCost;
moneyCost = aug.baseCost * multiplier * currentNodeMults.AugmentationMoneyCost;
moneyCost *= getBaseAugmentationPriceMultiplier() ** Player.queuedAugmentations.length;
moneyCost *= getGenericAugmentationPriceMultiplier();
break;
}
// SOA Augments use a unique cost method
@@ -124,17 +139,6 @@ export function getAugCost(aug: Augmentation): AugmentationCosts {
case AugmentationName.TrickeryOfHermes:
case AugmentationName.WKSharmonizer:
case AugmentationName.WisdomOfAthena: {
const soaAugmentationNames = [
AugmentationName.BeautyOfAphrodite,
AugmentationName.ChaosOfDionysus,
AugmentationName.FloodOfPoseidon,
AugmentationName.HuntOfArtemis,
AugmentationName.KnowledgeOfApollo,
AugmentationName.MightOfAres,
AugmentationName.TrickeryOfHermes,
AugmentationName.WKSharmonizer,
AugmentationName.WisdomOfAthena,
];
const soaAugCount = soaAugmentationNames.filter((augName) => Player.hasAugmentation(augName)).length;
moneyCost = aug.baseCost * Math.pow(CONSTANTS.SoACostMult, soaAugCount);
repCost = aug.baseRepRequirement * Math.pow(CONSTANTS.SoARepMult, soaAugCount);

View File

@@ -1013,9 +1013,9 @@ export const Augmentations: Record<AugmentationName, Augmentation> = (() => {
moneyCost: 1e6,
info:
"Extra-ocular neurons taken from old martial art master. Injecting them gives the user the ability to " +
"predict the enemy's attack before they even know it themselves.",
"predict the enemy's movement.",
stats:
"This augmentation makes the Slash minigame easier by showing you via an indicator when the slash is coming.",
"This augmentation makes the Slash minigame easier by showing you via an indicator when the sentinel is distracted.",
isSpecial: true,
factions: [FactionName.ShadowsOfAnarchy],
},

View File

@@ -28,9 +28,10 @@ function customFormatPercent(value: number): string {
}
function BitNodeModifiedStats(props: IBitNodeModifiedStatsProps): React.ReactElement {
// If player doesn't have SF5 or if the property isn't affected by BitNode mults
if (props.mult === 1 || Player.sourceFileLvl(5) === 0)
// If the player doesn't have access to SF5 feature or if the property isn't affected by BitNode mults
if (props.mult === 1 || (Player.bitNodeN !== 5 && Player.sourceFileLvl(5) === 0)) {
return <Typography color={props.color}>{customFormatPercent(props.base)}</Typography>;
}
return (
<Typography color={props.color}>

View File

@@ -2,7 +2,6 @@ import React from "react";
import { Player } from "@player";
import { CityName, FactionName } from "@enums";
import { BitNodeMultipliers, replaceCurrentNodeMults } from "./BitNodeMultipliers";
import { CorruptableText } from "../ui/React/CorruptableText";
class BitNode {
// A short description, or tagline, about the BitNode
@@ -37,23 +36,21 @@ export function initBitNodes() {
"The original BitNode",
(
<>
The first BitNode created by the Enders to imprison the minds of humans. It became the prototype and
testing-grounds for all of the BitNodes that followed.
This is the first BitNode created by the Enders to imprison the minds of humans. It became the prototype and
testing ground for all of the BitNodes that followed.
<br />
<br />
This is the first BitNode that you play through. It has no special modifications or mechanics.
<br />
<br />
Destroying this BitNode will give you Source-File 1, or if you already have this Source-File it will upgrade its
level up to a maximum of 3. This Source-File lets the player start with 32GB of RAM on his/her home computer
when entering a new BitNode, and also increases all of the player's multipliers by:
<br />
<br />
Level 1: 16%
<br />
Level 2: 24%
<br />
Level 3: 28%
Destroying this BitNode will give you Source-File 1, or if you already have this Source-File, it will upgrade
its level up to a maximum of 3. This Source-File lets the player start with 32GB of RAM on their home computer
when entering a new BitNode and increases all of the player's multipliers by:
<ul>
<li>Level 1: 16%</li>
<li>Level 2: 24%</li>
<li>Level 3: 28%</li>
</ul>
</>
),
);
@@ -61,36 +58,30 @@ export function initBitNodes() {
2,
0,
"Rise of the Underworld",
"From the shadows, they rose", //Gangs
"From the shadows, they rose",
(
<>
From the shadows, they rose.
<br />
<br />
Organized crime groups quickly filled the void of power left behind from the collapse of Western government in
the 2050s. As society and civilization broke down, people quickly succumbed to the innate human impulse of evil
and savagery. The organized crime factions quickly rose to the top of the modern world.
<br />
<br />
Certain Factions ({FactionName.SlumSnakes}, {FactionName.Tetrads}, {FactionName.TheSyndicate},{" "}
{FactionName.TheDarkArmy}, {FactionName.SpeakersForTheDead}, {FactionName.NiteSec}, {FactionName.TheBlackHand}
) give the player the ability to form and manage their own gang, which can earn the player money and reputation
with the corresponding Faction. Gangs offer more Augmentations than Factions, and in BitNode-2 offer a way to
destroy the BitNode.
Certain factions ({FactionName.SlumSnakes}, {FactionName.Tetrads}, {FactionName.TheSyndicate},{" "}
{FactionName.TheDarkArmy}, {FactionName.SpeakersForTheDead}, {FactionName.NiteSec}, and{" "}
{FactionName.TheBlackHand}) give the player the ability to form and manage their own gang, which can earn the
player money and reputation with the corresponding faction. The gang faction offers more augmentations than
other factions, and in BitNode-2, it offers a way to destroy the BitNode.
<br />
<br />
<br />
Destroying this BitNode will give you Source-File 2, or if you already have this Source-File it will upgrade its
level up to a maximum of 3. This Source-File allows you to form gangs in other BitNodes once your karma
Destroying this BitNode will give you Source-File 2, or if you already have this Source-File, it will upgrade
its level up to a maximum of 3. This Source-File allows you to form gangs in other BitNodes once your karma
decreases to a certain value. It also increases your crime success rate, crime money, and charisma multipliers
by:
<br />
<br />
Level 1: 24%
<br />
Level 2: 36%
<br />
Level 3: 42%
<ul>
<li>Level 1: 24%</li>
<li>Level 2: 36%</li>
<li>Level 3: 42%</li>
</ul>
</>
),
);
@@ -104,25 +95,24 @@ export function initBitNodes() {
Our greatest illusion is that a healthy society can revolve around a single-minded pursuit of wealth.
<br />
<br />
Sometime in the early 21st century economic and political globalization turned the world into a corporatocracy,
Sometime in the early 21st century, economic and political globalization turned the world into a corporatocracy,
and it never looked back. Now, the privileged elite will happily bankrupt their own countrymen, decimate their
own community, and evict their neighbors from houses in their desperate bid to increase their wealth.
<br />
<br />
In this BitNode you can create and manage your own corporation. Running a successful corporation has the
potential of generating massive profits.
In this BitNode, you can create and manage your own corporation. Running a successful corporation has the
potential to generate massive profits.
<br />
<br />
Destroying this BitNode will give you Source-File 3, or if you already have this Source-File it will upgrade its
level up to a maximum of 3. This Source-File lets you create corporations on other BitNodes (although some
Destroying this BitNode will give you Source-File 3, or if you already have this Source-File, it will upgrade
its level up to a maximum of 3. This Source-File lets you create corporations on other BitNodes (although some
BitNodes will disable this mechanic) and level 3 permanently unlocks the full API. This Source-File also
increases your charisma and company salary multipliers by:
<br />
Level 1: 8%
<br />
Level 2: 12%
<br />
Level 3: 14%
<ul>
<li>Level 1: 8%</li>
<li>Level 2: 12%</li>
<li>Level 3: 14%</li>
</ul>
</>
),
);
@@ -133,24 +123,23 @@ export function initBitNodes() {
"The Man and the Machine",
(
<>
The Singularity has arrived. The human race is gone, replaced by artificially superintelligent beings that are
more machine than man. <br />
The Singularity has arrived. The human race is gone, replaced by artificially super intelligent beings that are
more machine than man.
<br />
<br />
In this BitNode you will gain access to a new set of Netscript Functions known as Singularity Functions. These
In this BitNode, you will gain access to a new set of Netscript functions known as Singularity functions. These
functions allow you to control most aspects of the game through scripts, including working for
factions/companies, purchasing/installing Augmentations, and creating programs.
factions/companies, purchasing/installing augmentations, and creating programs.
<br />
<br />
Destroying this BitNode will give you Source-File 4, or if you already have this Source-File it will upgrade its
level up to a maximum of 3. This Source-File lets you access and use the Singularity Functions in other
Destroying this BitNode will give you Source-File 4, or if you already have this Source-File, it will upgrade
its level up to a maximum of 3. This Source-File lets you access and use the Singularity functions in other
BitNodes. Each level of this Source-File reduces the RAM cost of singularity functions:
<br />
Level 1: 16x
<br />
Level 2: 4x
<br />
Level 3: 1x
<ul>
<li>Level 1: 16x</li>
<li>Level 2: 4x</li>
<li>Level 3: 1x</li>
</ul>
</>
),
);
@@ -166,21 +155,20 @@ export function initBitNodes() {
that couldn't be modeled by 1's and 0's. They were wrong.
<br />
<br />
Destroying this BitNode will give you Source-File 5, or if you already have this Source-File it will upgrade its
level up to a maximum of 3. This Source-File grants you a special new stat called Intelligence. Intelligence is
unique because it is permanent and persistent (it never gets reset back to 1). However gaining Intelligence
Destroying this BitNode will give you Source-File 5, or if you already have this Source-File, it will upgrade
its level up to a maximum of 3. This Source-File grants you a special new stat called Intelligence. Intelligence
is unique because it is permanent and persistent (it never gets reset back to 1). However, gaining Intelligence
experience is much slower than other stats. Higher Intelligence levels will boost your production for many
actions in the game. <br />
actions in the game.
<br />
<br />
In addition, this Source-File will unlock the getBitNodeMultipliers() Netscript function and let you start with
Formulas.exe, and will also raise all of your hacking-related multipliers by:
<br />
<br />
Level 1: 8%
<br />
Level 2: 12%
<br />
Level 3: 14%
<ul>
<li>Level 1: 8%</li>
<li>Level 2: 12%</li>
<li>Level 3: 14%</li>
</ul>
</>
),
);
@@ -193,26 +181,24 @@ export function initBitNodes() {
<>
In the middle of the 21st century, {FactionName.OmniTekIncorporated} began designing and manufacturing advanced
synthetic androids, or Synthoids for short. They achieved a major technological breakthrough in the sixth
generation of their Synthoid design, called MK-VI, by developing a hyperintelligent AI. Many argue that this was
the first sentient AI ever created. This resulted in Synthoid models that were stronger, faster, and more
generation of their Synthoid design, called MK-VI, by developing a hyper-intelligent AI. Many argue that this
was the first sentient AI ever created. This resulted in Synthoid models that were stronger, faster, and more
intelligent than the humans that had created them.
<br />
<br />
In this BitNode you will be able to access the {FactionName.Bladeburners} Division at the NSA, which provides a
In this BitNode, you will be able to access the {FactionName.Bladeburners} division at the NSA, which provides a
new mechanic for progression.
<br />
<br />
Destroying this BitNode will give you Source-File 6, or if you already have this Source-File it will upgrade its
level up to a maximum of 3. This Source-File allows you to access the NSA's {FactionName.Bladeburners} Division
in other BitNodes. In addition, this Source-File will raise both the level and experience gain rate of all your
combat stats by:
<br />
<br />
Level 1: 8%
<br />
Level 2: 12%
<br />
Level 3: 14%
Destroying this BitNode will give you Source-File 6, or if you already have this Source-File, it will upgrade
its level up to a maximum of 3. This Source-File allows you to access the NSA's {FactionName.Bladeburners}{" "}
division in other BitNodes. In addition, this Source-File will raise both the level and experience gain rate of
all your combat stats by:
<ul>
<li>Level 1: 8%</li>
<li>Level 2: 12%</li>
<li>Level 3: 14%</li>
</ul>
</>
),
);
@@ -226,25 +212,23 @@ export function initBitNodes() {
In the middle of the 21st century, you were doing cutting-edge work at {FactionName.OmniTekIncorporated} as part
of the AI design team for advanced synthetic androids, or Synthoids for short. You helped achieve a major
technological breakthrough in the sixth generation of the company's Synthoid design, called MK-VI, by developing
a hyperintelligent AI. Many argue that this was the first sentient AI ever created. This resulted in Synthoid
a hyper-intelligent AI. Many argue that this was the first sentient AI ever created. This resulted in Synthoid
models that were stronger, faster, and more intelligent than the humans that had created them.
<br />
<br />
In this BitNode you will be able to access the {FactionName.Bladeburners} API, which allows you to access{" "}
In this BitNode, you will be able to access the {FactionName.Bladeburners} API, which allows you to access{" "}
{FactionName.Bladeburners} functionality through Netscript.
<br />
<br />
Destroying this BitNode will give you Source-File 7, or if you already have this Source-File it will upgrade its
level up to a maximum of 3. This Source-File allows you to access the {FactionName.Bladeburners} Netscript API
in other BitNodes. In addition, this Source-File will increase all of your {FactionName.Bladeburners}{" "}
Destroying this BitNode will give you Source-File 7, or if you already have this Source-File, it will upgrade
its level up to a maximum of 3. This Source-File allows you to access the {FactionName.Bladeburners} Netscript
API in other BitNodes. In addition, this Source-File will increase all of your {FactionName.Bladeburners}{" "}
multipliers by:
<br />
<br />
Level 1: 8%
<br />
Level 2: 12%
<br />
Level 3: 14%
<ul>
<li>Level 1: 8%</li>
<li>Level 2: 12%</li>
<li>Level 3: 14%</li>
</ul>
</>
),
);
@@ -259,33 +243,24 @@ export function initBitNodes() {
<br />
<br />
In this BitNode:
<br />
<br />
You start with $250 million
<br />
You start with a WSE membership and access to the TIX API
<br />
You are able to short stocks and place different types of orders (limit/stop)
<br />
<br />
Destroying this BitNode will give you Source-File 8, or if you already have this Source-File it will upgrade its
level up to a maximum of 3. This Source-File grants the following benefits:
<br />
<br />
Level 1: Permanent access to WSE and TIX API
<br />
Level 2: Ability to short stocks in other BitNodes
<br />
Level 3: Ability to use limit/stop orders in other BitNodes
<br />
<br />
<ul>
<li>You start with $250 million.</li>
<li>You start with a WSE membership and access to the TIX API.</li>
<li>You can short stocks and place different types of orders (limit/stop).</li>
</ul>
Destroying this BitNode will give you Source-File 8, or if you already have this Source-File, it will upgrade
its level up to a maximum of 3. This Source-File grants the following benefits:
<ul>
<li>Level 1: Permanent access to WSE and TIX API</li>
<li>Level 2: Ability to short stocks in other BitNodes</li>
<li>Level 3: Ability to use limit/stop orders in other BitNodes</li>
</ul>
This Source-File also increases your hacking growth multipliers by:
<br />
Level 1: 12%
<br />
Level 2: 18%
<br />
Level 3: 21%
<ul>
<li>Level 1: 12%</li>
<li>Level 2: 18%</li>
<li>Level 3: 21%</li>
</ul>
</>
),
);
@@ -298,36 +273,31 @@ export function initBitNodes() {
<>
When {FactionName.FulcrumSecretTechnologies} released their open-source Linux distro Chapeau, it quickly became
the OS of choice for the underground hacking community. Chapeau became especially notorious for powering the
Hacknet, a global, decentralized network used for nefarious purposes. {FactionName.FulcrumSecretTechnologies}{" "}
quickly abandoned the project and dissociated themselves from it.
Hacknet, which is a global, decentralized network used for nefarious purposes.{" "}
{FactionName.FulcrumSecretTechnologies} quickly abandoned the project and dissociated themselves from it.
<br />
<br />
This BitNode unlocks the Hacknet Server, an upgraded version of the Hacknet Node. Hacknet Servers generate
hashes, which can be spent on a variety of different upgrades.
<br />
<br />
Destroying this BitNode will give you Source-File 9, or if you already have this Source-File it will upgrade its
level up to a maximum of 3. This Source-File grants the following benefits:
<br />
<br />
Level 1: Permanently unlocks the Hacknet Server in other BitNodes
<br />
Level 2: You start with 128GB of RAM on your home computer when entering a new BitNode
<br />
Level 3: Grants a highly-upgraded Hacknet Server when entering a new BitNode
This BitNode unlocks the Hacknet Server, which is an upgraded version of the Hacknet Node. Hacknet Servers
generate hashes, which can be spent on a variety of different upgrades.
<br />
<br />
Destroying this BitNode will give you Source-File 9, or if you already have this Source-File, it will upgrade
its level up to a maximum of 3. This Source-File grants the following benefits:
<ul>
<li>Level 1: Permanently unlocks the Hacknet Server in other BitNodes</li>
<li>Level 2: You start with 128GB of RAM on your home computer when entering a new BitNode</li>
<li>Level 3: Grants a highly-upgraded Hacknet Server when entering a new BitNode</li>
</ul>
(Note that the Level 3 effect of this Source-File only applies when entering a new BitNode, NOT when installing
Augmentations)
augmentations)
<br />
<br />
This Source-File also increases hacknet production and reduces hacknet costs by:
<br />
Level 1: 12%
<br />
Level 2: 18%
<br />
Level 3: 21%
<ul>
<li>Level 1: 12%</li>
<li>Level 2: 18%</li>
<li>Level 3: 21%</li>
</ul>
</>
),
);
@@ -344,21 +314,20 @@ export function initBitNodes() {
achieved immortality - at least for those that could afford it.
<br />
<br />
This BitNode unlocks Sleeve and grafting technologies. Sleeve technology allows you to:
<br />
<br />
1. Grafting: Visit VitaLife in New Tokyo to be able to obtain Augmentations without needing to install
<br />
2. Duplicate Sleeves: Duplicate your consciousness into Synthoids, allowing you to perform different tasks
synchronously.
<br />
<br />
Grafting technology allows you to graft Augmentations, which is an alternative way of installing Augmentations.
<br />
<br />
Destroying this BitNode will give you Source-File 10, or if you already have this Source-File it will upgrade
its level up to a maximum of 3. This Source-File unlocks Sleeve technology, and the Grafting API in other
BitNodes. Each level of this Source-File also grants you a Duplicate Sleeve
This BitNode unlocks Sleeve and Grafting technology:
<ul>
<li>
Sleeve: Duplicate your consciousness into Synthoids, allowing you to perform different tasks asynchronously.
You cannot buy Sleeves outside this BitNode.
</li>
<li>
Grafting: Visit VitaLife in New Tokyo to get access to this technology. It allows you to graft
augmentations, which is an alternative way of installing augmentations.
</li>
</ul>
Destroying this BitNode will give you Source-File 10, or if you already have this Source-File, it will upgrade
its level up to a maximum of 3. This Source-File unlocks Sleeve and Grafting API in other BitNodes. Each level
of this Source-File also grants you a Sleeve.
</>
),
);
@@ -370,9 +339,9 @@ export function initBitNodes() {
(
<>
The 2050s was defined by the massive amounts of violent civil unrest and anarchic rebellion that rose all around
the world. It was this period of disorder that eventually lead to the governmental reformation of many global
the world. It was this period of disorder that eventually led to the governmental reformation of many global
superpowers, most notably the USA and China. But just as the world was slowly beginning to recover from these
dark times, financial catastrophe hit.
dark times, financial catastrophes hit.
<br />
<br />
In many countries, the high cost of trying to deal with the civil disorder bankrupted the governments. In all of
@@ -381,27 +350,21 @@ export function initBitNodes() {
the world is slowly crumbling in the middle of the biggest economic crisis of all time.
<br />
<br />
Destroying this BitNode will give you Source-File 11, or if you already have this Source-File it will upgrade
Destroying this BitNode will give you Source-File 11, or if you already have this Source-File, it will upgrade
its level up to a maximum of 3. This Source-File makes it so that company favor increases BOTH the player's
salary and reputation gain rate at that company by 1% per favor (rather than just the reputation gain). This
Source-File also increases the player's company salary and reputation gain multipliers by:
<br />
<br />
Level 1: 32%
<br />
Level 2: 48%
<br />
Level 3: 56%
<br />
<br />
It also reduces the price increase for every aug bought by:
<br />
<br />
Level 1: 4%
<br />
Level 2: 6%
<br />
Level 3: 7%
<ul>
<li>Level 1: 32%</li>
<li>Level 2: 48%</li>
<li>Level 3: 56%</li>
</ul>
It also reduces the price increase for every augmentation bought by:
<ul>
<li>Level 1: 4%</li>
<li>Level 2: 6%</li>
<li>Level 3: 7%</li>
</ul>
</>
),
);
@@ -412,13 +375,13 @@ export function initBitNodes() {
"Repeat.",
(
<>
To iterate is human, to recurse divine.
To iterate is human; to recurse, divine.
<br />
<br />
Every time this BitNode is destroyed, it becomes slightly harder. Destroying this BitNode will give you
Source-File 12, or if you already have this Source-File it will upgrade its level. There is no maximum level for
Source-File 12. Each level of Source-File 12 lets you start any BitNodes with NeuroFlux Governor equal to the
level of this source file.
Source-File 12, or if you already have this Source-File, it will upgrade its level. There is no maximum level
for Source-File 12. Each level of Source-File 12 lets you start any BitNodes with NeuroFlux Governor equal to
the level of this source file.
</>
),
);
@@ -429,15 +392,15 @@ export function initBitNodes() {
"1 step back, 2 steps forward",
(
<>
With the invention of Augmentations in the 2040s a religious group known as the{" "}
With the invention of augmentations in the 2040s, a religious group known as the{" "}
{FactionName.ChurchOfTheMachineGod} has rallied far more support than anyone would have hoped.
<br />
<br />
Their leader, Allison "Mother" Stanek is said to have created her own Augmentation whose power goes beyond any
Their leader, Allison "Mother" Stanek is said to have created her own augmentation whose power goes beyond any
other. Find her in {CityName.Chongqing} and gain her trust.
<br />
<br />
Destroying this BitNode will give you Source-File 13, or if you already have this Source-File it will upgrade
Destroying this BitNode will give you Source-File 13, or if you already have this Source-File, it will upgrade
its level up to a maximum of 3. This Source-File lets the {FactionName.ChurchOfTheMachineGod} appear in other
BitNodes.
<br />
@@ -460,40 +423,22 @@ export function initBitNodes() {
networks by controlling the open space in the 'net!
<br />
<br />
Destroying this BitNode will give you Source-File 14, or if you already have this Source-File it will upgrade
Destroying this BitNode will give you Source-File 14, or if you already have this Source-File, it will upgrade
its level up to a maximum of 3. This Source-File grants the following benefits:
<br />
<br />
Level 1: 100% increased stat multipliers from Node Power
<br />
Level 2: Permanently unlocks the go.cheat API
<br />
Level 3: 25% additive increased success rate for the go.cheat API
<br />
<br />
<ul>
<li>Level 1: 100% increased stat multipliers from Node Power</li>
<li>Level 2: Permanently unlocks the go.cheat API</li>
<li>Level 3: 25% additive increased success rate for the go.cheat API</li>
</ul>
This Source-File also increases the maximum favor you can gain for each faction from IPvGO to:
<br />
Level 1: 80
<br />
Level 2: 100
<br />
Level 3: 120
<ul>
<li>Level 1: 80</li>
<li>Level 2: 100</li>
<li>Level 3: 120</li>
</ul>
</>
),
);
BitNodes.BitNode19 = new BitNode(
19,
2,
"MyrianOS",
"l̷i̵g̵h̴t̵ ̴a̷t̸ ̶t̵h̵e̸ ̶e̷n̵d̶ ̶o̸f̶ ̸t̴h̸e̴ ̸t̷u̶n̸n̸e̷l̵.̷",
(
<CorruptableText
content={`yNjHLAgecI ASW1fQdKx5 n9DQ3rmHp3 mnv0XEdwH2 sBkAlBOPhx NohIDL9eRy TbIl8U3WKz 1wjnJ9iuwS VML36vYLNH K06StviNvI cRboTarefZ 7BSNntPpJj DfayVbfxU6 46xvOPQd2Y Ogyj2gnyLr FIND THE GLITCH IN ISHIMA S6E0Vpmxk6 GTF9dWvE6n EEGg7xvtYR Um8YIC0Qww PG4vauBKBk JWG8V1j5Z5 bfYYTTFnBY 7uoicoqIaV IeUu0F42aA EhTF7Fkxyt OBYgGSu0es bJQpenVoO6 L9cL39tRhh xfLroUMvY8 xmMckUHLSQ`}
spoiler={false}
/>
),
);
}
export const defaultMultipliers = new BitNodeMultipliers();
@@ -1001,64 +946,6 @@ export function getBitNodeMultipliers(n: number, lvl: number): BitNodeMultiplier
WorldDaemonDifficulty: 5,
});
}
case 19: {
return new BitNodeMultipliers({
MyrianPower: 10,
AgilityLevelMultiplier: 0.01,
AugmentationMoneyCost: 100,
AugmentationRepCost: 100,
BladeburnerRank: 0.01,
BladeburnerSkillCost: 100,
CharismaLevelMultiplier: 0.01,
ClassGymExpGain: 0.01,
CodingContractMoney: 0.01,
CompanyWorkExpGain: 0.01,
CompanyWorkMoney: 0.01,
CompanyWorkRepGain: 0.01,
CorporationValuation: 0.01,
CrimeExpGain: 0.01,
CrimeMoney: 0.01,
CrimeSuccessRate: 0.01,
DaedalusAugsRequirement: 100,
DefenseLevelMultiplier: 0.01,
DexterityLevelMultiplier: 0.01,
FactionPassiveRepGain: 0.01,
FactionWorkExpGain: 0.01,
FactionWorkRepGain: 0.01,
FourSigmaMarketDataApiCost: 100,
FourSigmaMarketDataCost: 100,
GangSoftcap: 0.01,
GangUniqueAugs: 0.01,
GoPower: 0.01,
HackExpGain: 0.01,
HackingLevelMultiplier: 0.01,
HackingSpeedMultiplier: 0.01,
HacknetNodeMoney: 0.01,
HomeComputerRamCost: 100,
InfiltrationMoney: 0.01,
InfiltrationRep: 0.01,
ManualHackMoney: 0.01,
PurchasedServerCost: 100,
PurchasedServerSoftcap: 100,
PurchasedServerLimit: 0.01,
PurchasedServerMaxRam: 0.01,
RepToDonateToFaction: 10000,
ScriptHackMoney: 0.01,
ScriptHackMoneyGain: 0.01,
ServerGrowthRate: 0.01,
ServerMaxMoney: 0.01,
ServerStartingMoney: 0.01,
ServerStartingSecurity: 100,
ServerWeakenRate: 0.01,
StrengthLevelMultiplier: 0.01,
StaneksGiftPowerMultiplier: 0.01,
StaneksGiftExtraSize: -100,
WorldDaemonDifficulty: 100,
CorporationSoftcap: 0.01,
CorporationDivisions: 0.01,
});
}
default: {
throw new Error("Invalid BitNodeN");
}

View File

@@ -114,9 +114,6 @@ export class BitNodeMultipliers {
*/
ManualHackMoney = 1;
/** Influence how strongly Myrian improves bitnode multipliers */
MyrianPower = 1;
/** Influence how much it costs to purchase a server */
PurchasedServerCost = 1;

View File

@@ -236,7 +236,7 @@ export function BitverseRoot(props: IProps): React.ReactElement {
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> O </Typography>
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> | O O | O O | </Typography>
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> O | | / __| \ | | O </Typography>
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> O | O | | <BitNodePortal n={19} level={n(19)} flume={props.flume} destroyedBitNode={destroyed} /> / | O | | O | O </Typography>
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> O | O | | O / | O | | O | O </Typography>
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> | | | | |_/ |/ | \_ \_| | | | | </Typography>
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> O | | | <BitNodePortal n={14} level={n(14)} flume={props.flume} destroyedBitNode={destroyed} /> | | O__/ | / \__ | | O | | | O </Typography>
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> | | | | | | | / /| O / \| | | | | | | </Typography>

View File

@@ -28,19 +28,18 @@ export function PortalModal(props: IProps): React.ReactElement {
<Typography variant="h4">
BitNode-{props.n}: {bitNode.name}
</Typography>
<Typography variant="h5">{bitNode.desc}</Typography>
<br />
<Typography>
Source-File Level: {props.level} / {maxSourceFileLevel}
</Typography>
<br />
<br />
<Typography> Difficulty: {["easy", "normal", "hard"][bitNode.difficulty]}</Typography>
<br />
<br />
<Typography>{bitNode.info}</Typography>
<Typography component="div">{bitNode.info}</Typography>
<BitnodeMultiplierDescription n={props.n} level={newLevel} />
<br />
<br />
<Button
aria-label={`enter-bitnode-${bitNode.number.toString()}`}
autoFocus={true}

View File

@@ -8,6 +8,7 @@ import { BladeburnerConstants } from "../data/Constants";
import { calculateIntelligenceBonus } from "../../PersonObjects/formulas/intelligence";
import { BladeMultName } from "../Enums";
import { getRecordKeys } from "../../Types/Record";
import { clampNumber } from "../../utils/helpers/clampNumber";
export interface ActionParams {
desc: string;
@@ -146,9 +147,16 @@ export abstract class ActionClass {
let high = real + diff;
const city = bladeburner.getCurrentCity();
let r = city.pop / city.popEst;
if (Number.isNaN(r)) r = 0;
if (r < 1) low *= r;
else high *= r;
if (Number.isNaN(r)) {
r = 0;
}
if (r < 1) {
low *= r;
} else {
// We need to "clamp" r with "clampNumber" (not "clamp"), otherwise (high *= r) may be NaN. This happens when the
// action is Raid, popEst=0, and comms=0.
high *= clampNumber(r);
}
return [clamp(low), clamp(high)];
}

View File

@@ -34,18 +34,20 @@ export class BlackOperation extends ActionClass {
if (bladeburner.rank < this.reqdRank) return { error: "Insufficient rank" };
return { available: true };
}
// To be implemented by subtypes
getActionTimePenalty(): number {
return 1.5;
}
getPopulationSuccessFactor(/*inst: Bladeburner, params: ISuccessChanceParams*/): number {
getPopulationSuccessFactor(): number {
return 1;
}
getChaosSuccessFactor(/*inst: Bladeburner, params: ISuccessChanceParams*/): number {
getChaosSuccessFactor(): number {
return 1;
}
getTeamSuccessBonus = operationTeamSuccessBonus;
getActionTypeSkillSuccessBonus = operationSkillSuccessBonus;
}

View File

@@ -32,6 +32,7 @@ export class Operation extends LevelableActionClass {
// These functions are shared between operations and blackops, so they are defined outside of Operation
getTeamSuccessBonus = operationTeamSuccessBonus;
getActionTypeSkillSuccessBonus = operationSkillSuccessBonus;
getChaosSuccessFactor(inst: Bladeburner /*, params: ISuccessChanceParams*/): number {
@@ -45,7 +46,9 @@ export class Operation extends LevelableActionClass {
return 1;
}
getSuccessChance(inst: Bladeburner, person: Person, params: SuccessChanceParams) {
if (this.name == BladeOperationName.raid && inst.getCurrentCity().comms <= 0) return 0;
if (this.name === BladeOperationName.raid && inst.getCurrentCity().comms <= 0) {
return 0;
}
return ActionClass.prototype.getSuccessChance.call(this, inst, person, params);
}
@@ -57,6 +60,7 @@ export class Operation extends LevelableActionClass {
toJSON(): IReviverValue {
return this.save("Operation", "teamCount");
}
loadData(loadedObject: Operation): void {
this.teamCount = clampInteger(loadedObject.teamCount, 0);
LevelableActionClass.prototype.loadData.call(this, loadedObject);
@@ -73,6 +77,7 @@ constructorsForReviver.Operation = Operation;
export const operationSkillSuccessBonus = (inst: Bladeburner) => {
return inst.getSkillMult(BladeMultName.successChanceOperation);
};
export function operationTeamSuccessBonus(this: Operation | BlackOperation, inst: Bladeburner) {
if (this.teamCount && this.teamCount > 0) {
this.teamCount = Math.min(this.teamCount, inst.teamSize);

View File

@@ -125,7 +125,7 @@ export class Bladeburner {
startAction(actionId: ActionIdentifier | null): Attempt<{ message: string }> {
if (!actionId) {
this.resetAction();
return { success: true, message: "Stopped current bladeburner action" };
return { success: true, message: "Stopped current Bladeburner action" };
}
if (!Player.hasAugmentation(AugmentationName.BladesSimulacrum, true)) Player.finishWork(true);
const action = this.getActionObject(actionId);

View File

@@ -11,18 +11,51 @@ import { TeamSizeButton } from "./TeamSizeButton";
import { formatNumberNoSuffix } from "../../ui/formatNumber";
import { BlackOperation, Operation } from "../Actions";
import { BladeburnerConstants } from "../data/Constants";
import { convertTimeMsToTimeElapsedString } from "../../utils/StringHelperFunctions";
interface ActionHeaderProps {
bladeburner: Bladeburner;
action: Action;
rerender: () => void;
}
export function ActionHeader({ bladeburner, action, rerender }: ActionHeaderProps): React.ReactElement {
const isActive = action.name === bladeburner.action?.name;
const computedActionTimeCurrent = Math.min(
bladeburner.actionTimeCurrent + bladeburner.actionTimeOverflow,
bladeburner.actionTimeToComplete,
);
const remainingSeconds = Math.max(
bladeburner.actionTimeToComplete - bladeburner.actionTimeCurrent + bladeburner.actionTimeOverflow,
0,
);
const remainingBonusSeconds = Math.floor(bladeburner.storedCycles / BladeburnerConstants.CyclesPerSecond);
/**
* Bladeburner is processed every second. Each time it's processed, we use (up to) 4 bonus seconds and process it as
* if (up to) 5 seconds passed.
* For example, with 20 bonus seconds, we need 5 seconds to use up all those bonus seconds. After 5 seconds, we used
* up 20 bonus seconds and processed Bladeburner as if 25 seconds had passed.
*/
const effectiveBonusSeconds = (remainingBonusSeconds / 4) * 5;
let eta;
if (remainingSeconds <= effectiveBonusSeconds) {
// If we have enough effectiveBonusSeconds, ETA is (remainingSeconds / 5).
eta = Math.floor(remainingSeconds / 5);
} else {
/**
* For example, let's say we start the "Training" action with 20 bonus seconds: remainingSeconds=30;remainingBonusSeconds=20.
* After 5 seconds (remainingBonusSeconds / 4), we processed Bladeburner as if 25 seconds (effectiveBonusSeconds)
* had passed. We still need 5 more seconds (30 - 25 = remainingTime - effectiveBonusSeconds) to complete the action
* at normal speed.
*
* ETA = remainingBonusSeconds / 4 + remainingTime - effectiveBonusSeconds
* = remainingBonusSeconds / 4 + remainingTime - ((remainingBonusSeconds / 4) * 5)
* = remainingTime - remainingBonusSeconds
*/
eta = remainingSeconds - remainingBonusSeconds;
}
const allowTeam = action instanceof Operation || action instanceof BlackOperation;
if (isActive) {
@@ -36,11 +69,14 @@ export function ActionHeader({ bladeburner, action, rerender }: ActionHeaderProp
(IN PROGRESS - {formatNumberNoSuffix(computedActionTimeCurrent, 0)} /{" "}
{formatNumberNoSuffix(bladeburner.actionTimeToComplete, 0)})
</Typography>
<Typography>
{createProgressBarText({
progress: computedActionTimeCurrent / bladeburner.actionTimeToComplete,
})}
</Typography>
<Box display="flex" flexDirection="row" alignItems="center">
<Typography>
{createProgressBarText({
progress: computedActionTimeCurrent / bladeburner.actionTimeToComplete,
})}
</Typography>
<Typography marginLeft="1rem">Remaining time: {convertTimeMsToTimeElapsedString(eta * 1000)}</Typography>
</Box>
</>
);
}

View File

@@ -130,7 +130,7 @@ export function Stats({ bladeburner }: StatsProps): React.ReactElement {
</Tooltip>
</Box>
<br />
{(bladeburner.storedCycles / BladeburnerConstants.CyclesPerSecond) * 1000 > 15000 && (
{bladeburner.storedCycles / BladeburnerConstants.CyclesPerSecond > 3 && (
<>
<Box display="flex">
<Tooltip

View File

@@ -55,9 +55,9 @@ export const CONSTANTS: {
CompanyRequiredReputationMultiplier: number; // Only use this if a backdoor is installed in the company's server
LatestUpdate: string;
} = {
VersionString: "2.6.2dev",
isDevBranch: true,
VersionNumber: 39,
VersionString: "2.6.2",
isDevBranch: false,
VersionNumber: 40,
/** 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
@@ -156,10 +156,72 @@ export const CONSTANTS: {
// Also update doc/source/changelog.rst
LatestUpdate: `
## v2.6.2 dev - Last update 22 May 2024
## v2.6.2 Release: 3 July 2024
See 2.6.1 changelog at https://github.com/bitburner-official/bitburner-src/blob/v2.6.1/src/Documentation/doc/changelog.md
### CHANGES
No changes yet since 2.6.1 release
- Hotfix (also backported to 2.6.1): Fixed an issue with invalid format on steam cloud save (@catloversg)
- Augmentations: Augmentations that affect starting money now add money instead of replacing it (@jjclark1982)
- API: ns.spawn can now be used with 0 delay for a synchronous effect (@d0sboots)
- API: Added the ns.ramOverride function, which allows altering the ram usage of a script at runtime (@d0sboots)
- Coding Contracts: Improved the performance of the All Valid Math Expressions contract checker (@yichizhng)
- Coding Contracts: Simplified the Shortest Path contract checker (@gmcew)
- Documentation: Various improvements (@mrsimo, @catloversg, @ficocelliguy, @gmcew, @otac0n)
- Electron: Game can now load correctly when the path includes symbolic links (@catloversg)
- Faction: Fixed some edge cases around Favor overflow (@catloversg)
- Faction: All available invites are sent at once (@catloversg)
- Faction UI: show which skills are relevant for each type of Faction work (@gmcew)
- Font: Embedded the JetBrains Mono font as "JetBrainsMono" (@catloversg)
- Go: Can now play manually as white against your own scripts (@ficocelliguy)
- Go: Save a full game history to prevent repeat moves (@ficocelliguy)
- Go: Support offline time / bonus cycles to allow less time between AI moved (@ficocelliguy)
- Hacking: Clamp hack success chance to prevent issues with infinity (@Caldwell-74)
- Hacknet: Fix an issue that caused inaccurate level base cost (@JamesWilcox-git)
- Hacknet: UI improvements (@jjclark1982)
- Hospital: Can now be hospitalized even when busy performing a work task (@catloversg)
- Infiltration: Automating infiltration is now more difficult (@catloversg)
- Infiltration: Wire game shows wire colors on wires (@muesli4brekkies)
- Misc: Changed how negative changes in system time are handled (@catloversg)
- Programs UI: Now displays time remaining (@TheAimMan)
- Servers: Existing servers can now have more than 1 core (@TheAimMan)
- Scripts: Relative imports can now be used (@Caldwell-74)
- Script Editor: Improved detection of possible infinite loops (@G4mingJon4s)
- Script Editor: Cursor location is remembered when switching tabs or game pages (@catloversg)
- Script Editor: Improvements to vim mode (@G4mingJon4s)
- Script Editor: Individual script tabs can be in separate editor modes (@G4mingJon4s)
- Skills: Fix an inconsistency in experience needed to level a skill from 1 to 2 (@catloversg)
- Terminal: Add more options to the rm command (@G4mingJon4s, @d0sboots)
- Terminal: Added grep command (@muesli4brekkies)
- Terminal: Improved autocompletion for mixed case strings (@yichizhng)
- Codebase: Partial migration away from outdated mui/styles (@Caldwell-74)
- Codebase: Cleanup / refactoring (@catloversg, @Caldwell-74, @Snarling, @ficocelliguy, @tomprince)
### SPOILER CHANGES
- Bladeburner: Added a button to stop the current action (@Kelenius, @catloversg)
- Bladeburner: Improved logging of information in the Bladeburner console (@Kelenius, @catloversg)
- Bladeburner: Black Operations show in the expected order again (@catloversg)
- Bitnode 5: Show bitnode multipliers while in BN5.1 (@catloversg)
- Bitverse: Spawned scripts will no longer launch from the bitverse screen (@catloversg)
- Corporation: Refactor markup multiplier (@catloversg)
- Corporation: Allow mass discarding products by selling for 0 (@gmcew)
- Corporation: Allow access to constants even when API access is not available (@ilkecan)
- Gang: Show equipment even when it cannot be purchased yet (@catloversg)
- Gang: Fix an issue with wanted gain rate (@catloversg)
- Gang: Show effective gain rates when bonus time in effect (@G4mingJon4s)
- Grafting: Fixed a spacing issue (@Sphyxis)
- Grafting: Fixed an issue that could cause hacknet node production to be inaccurrate when combined with Grafting (@catloversg)
- Grafting: Fixed an issue that could cause inaccurate HP after Grafting (@catloversg)
- Grafting: Added ns.grafting.waitForOngoingGrafting (@catloversg)
- Intelligence: Changed scaling for intelligence gain from manual hacking (@catloversg)
- Sleeve: Sleeve travel can no longer be performed if the player has insufficient funds (@gmcew)
- Sleeve: It's no longer possible to install an unavailable augmentation on a sleeve (@yichizhng)
- Sleeve: No longer show a dialog message if a sleeve is working at a job while quitting that company (@Kelenius)
- Sleeve: ns.sleeve.setToBladeburnerAction works again for setting sleeves to Bladeburner contract work (@Sphyxis)
- Singularity: Add ns.singularity.getFactionWorkTypes (@catloversg)
- Singularity: Fix an edge case issue with ns.singularity.getAugmentationFactions (@catloversg)
### OTHER
- Nerf noodle bar
`,
};

View File

@@ -574,8 +574,7 @@ export class Division {
sCost = optimalPrice;
} else if (mat.marketTa1) {
sCost = mat.marketPrice + markupLimit;
// check truthyness to avoid unnecessary eval
} else if (typeof mat.desiredSellPrice === "string" && mat.desiredSellPrice) {
} else if (typeof mat.desiredSellPrice === "string") {
sCost = mat.desiredSellPrice.replace(/MP/g, mat.marketPrice.toString());
sCost = eval(sCost);
} else {
@@ -903,7 +902,7 @@ export class Division {
product.markup = 1;
}
sCostString = sCostString.replace(/MP/g, product.cityData[city].productionCost.toString());
sCost = Math.max(product.cityData[city].productionCost, eval(sCostString));
sCost = eval(sCostString);
} else {
sCost = sellPrice;
}

View File

@@ -45,3 +45,196 @@ The only things that will persist through destroying BitNodes are:
- [Source-Files](sourcefiles.md)
- [Scripts](../basic/scripts.md) on the home computer
- [Intelligence](intelligence.md)
## BitNode list
### BitNode 1: Source Genesis
This is the first BitNode created by the Enders to imprison the minds of humans. It became the prototype and testing ground for all of the BitNodes that followed.
This is the first BitNode that you play through. It has no special modifications or mechanics.
Destroying this BitNode will give you Source-File 1, or if you already have this Source-File, it will upgrade its level up to a maximum of 3. This Source-File lets the player start with 32GB of RAM on their home computer when entering a new BitNode and increases all of the player's multipliers by:
- Level 1: 16%
- Level 2: 24%
- Level 3: 28%
### BitNode 2: Rise of the Underworld
Organized crime groups quickly filled the void of power left behind from the collapse of Western government in the 2050s. As society and civilization broke down, people quickly succumbed to the innate human impulse of evil and savagery. The organized crime factions quickly rose to the top of the modern world.
Certain factions (Slum Snakes, Tetrads, The Syndicate, The Dark Army, Speakers for the Dead, NiteSec, and The Black Hand) give the player the ability to form and manage their own gang, which can earn the player money and reputation with the corresponding faction. The gang faction offers more augmentations than other factions, and in BitNode-2, it offers a way to destroy the BitNode.
Destroying this BitNode will give you Source-File 2, or if you already have this Source-File, it will upgrade its level up to a maximum of 3. This Source-File allows you to form gangs in other BitNodes once your karma decreases to a certain value. It also increases your crime success rate, crime money, and charisma multipliers by:
- Level 1: 24%
- Level 2: 36%
- Level 3: 42%
### BitNode 3: Corporatocracy
Our greatest illusion is that a healthy society can revolve around a single-minded pursuit of wealth.
Sometime in the early 21st century, economic and political globalization turned the world into a corporatocracy, and it never looked back. Now, the privileged elite will happily bankrupt their own countrymen, decimate their own community, and evict their neighbors from houses in their desperate bid to increase their wealth.
In this BitNode, you can create and manage your own corporation. Running a successful corporation has the potential to generate massive profits.
Destroying this BitNode will give you Source-File 3, or if you already have this Source-File, it will upgrade its level up to a maximum of 3. This Source-File lets you create corporations on other BitNodes (although some BitNodes will disable this mechanic) and level 3 permanently unlocks the full API. This Source-File also increases your charisma and company salary multipliers by:
- Level 1: 8%
- Level 2: 12%
- Level 3: 14%
### BitNode 4: The Singularity
The Singularity has arrived. The human race is gone, replaced by artificially super intelligent beings that are more machine than man.
In this BitNode, you will gain access to a new set of Netscript functions known as Singularity functions. These functions allow you to control most aspects of the game through scripts, including working for factions/companies, purchasing/installing augmentations, and creating programs.
Destroying this BitNode will give you Source-File 4, or if you already have this Source-File, it will upgrade its level up to a maximum of 3. This Source-File lets you access and use the Singularity functions in other BitNodes. Each level of this Source-File reduces the RAM cost of singularity functions:
- Level 1: 16x
- Level 2: 4x
- Level 3: 1x
### BitNode 5: Artificial Intelligence
They said it couldn't be done. They said the human brain, along with its consciousness and intelligence, couldn't be replicated. They said the complexity of the brain results from unpredictable, nonlinear interactions that couldn't be modeled by 1's and 0's. They were wrong.
Destroying this BitNode will give you Source-File 5, or if you already have this Source-File, it will upgrade its level up to a maximum of 3. This Source-File grants you a special new stat called Intelligence. Intelligence is unique because it is permanent and persistent (it never gets reset back to 1). However, gaining Intelligence
experience is much slower than other stats. Higher Intelligence levels will boost your production for many actions in the game.
In addition, this Source-File will unlock the getBitNodeMultipliers() Netscript function and let you start with Formulas.exe, and will also raise all of your hacking-related multipliers by:
- Level 1: 8%
- Level 2: 12%
- Level 3: 14%
### BitNode 6: Bladeburners
In the middle of the 21st century, OmniTek Incorporated began designing and manufacturing advanced synthetic androids, or Synthoids for short. They achieved a major technological breakthrough in the sixth generation of their Synthoid design, called MK-VI, by developing a hyper-intelligent AI. Many argue that this was the first sentient AI ever created. This resulted in Synthoid models that were stronger, faster, and more intelligent than the humans that had created them.
In this BitNode, you will be able to access the Bladeburners division at the NSA, which provides a new mechanic for progression.
Destroying this BitNode will give you Source-File 6, or if you already have this Source-File, it will upgrade its level up to a maximum of 3. This Source-File allows you to access the NSA's Bladeburners division in other BitNodes. In addition, this Source-File will raise both the level and experience gain rate of all your combat stats by:
- Level 1: 8%
- Level 2: 12%
- Level 3: 14%
### BitNode 7: Bladeburners 2079
In the middle of the 21st century, you were doing cutting-edge work at OmniTek Incorporated as part of the AI design team for advanced synthetic androids, or Synthoids for short. You helped achieve a major technological breakthrough in the sixth generation of the company's Synthoid design, called MK-VI, by developing a hyper-intelligent AI. Many argue that this was the first sentient AI ever created. This resulted in Synthoid models that were stronger, faster, and more intelligent than the humans that had created them.
In this BitNode, you will be able to access the Bladeburners API, which allows you to access Bladeburners functionality through Netscript.
Destroying this BitNode will give you Source-File 7, or if you already have this Source-File, it will upgrade its level up to a maximum of 3. This Source-File allows you to access the Bladeburners Netscript API in other BitNodes. In addition, this Source-File will increase all of your Bladeburners multipliers by:
- Level 1: 8%
- Level 2: 12%
- Level 3: 14%
### BitNode 8: Ghost of Wall Street
You are trying to make a name for yourself as an up-and-coming hedge fund manager on Wall Street.
In this BitNode:
- You start with $250 million
- You start with a WSE membership and access to the TIX API
- You are able to short stocks and place different types of orders (limit/stop)
Destroying this BitNode will give you Source-File 8, or if you already have this Source-File, it will upgrade its level up to a maximum of 3. This Source-File grants the following benefits:
- Level 1: Permanent access to WSE and TIX API
- Level 2: Ability to short stocks in other BitNodes
- Level 3: Ability to use limit/stop orders in other BitNodes
This Source-File also increases your hacking growth multipliers by:
- Level 1: 12%
- Level 2: 18%
- Level 3: 21%
### BitNode 9: Hacktocracy
When Fulcrum Secret Technologies released their open-source Linux distro Chapeau, it quickly became the OS of choice for the underground hacking community. Chapeau became especially notorious for powering the Hacknet, which is a global, decentralized network used for nefarious purposes. Fulcrum Secret Technologies quickly abandoned the project and dissociated themselves from it.
This BitNode unlocks the Hacknet Server, which is an upgraded version of the Hacknet Node. Hacknet Servers generate hashes, which can be spent on a variety of different upgrades.
Destroying this BitNode will give you Source-File 9, or if you already have this Source-File, it will upgrade its level up to a maximum of 3. This Source-File grants the following benefits:
- Level 1: Permanently unlocks the Hacknet Server in other BitNodes
- Level 2: You start with 128GB of RAM on your home computer when entering a new BitNode
- Level 3: Grants a highly-upgraded Hacknet Server when entering a new BitNode
(Note that the Level 3 effect of this Source-File only applies when entering a new BitNode, NOT when installing augmentations)
This Source-File also increases hacknet production and reduces hacknet costs by:
- Level 1: 12%
- Level 2: 18%
- Level 3: 21%
### BitNode 10: Digital Carbon
In 2084, VitaLife unveiled to the world the Persona Core, a technology that allowed people to digitize their consciousness. Their consciousness could then be transferred into Synthoids or other bodies by transmitting the digitized data. Human bodies became nothing more than 'sleeves' for the human consciousness. Mankind had finally
achieved immortality - at least for those that could afford it.
This BitNode unlocks Sleeve and Grafting technology:
- Sleeve: Duplicate your consciousness into Synthoids, allowing you to perform different tasks asynchronously. You cannot buy Sleeves outside this BitNode.
- Grafting: Visit VitaLife in New Tokyo to get access to this technology. It allows you to graft augmentations, which is an alternative way of installing augmentations.
Destroying this BitNode will give you Source-File 10, or if you already have this Source-File, it will upgrade its level up to a maximum of 3. This Source-File unlocks Sleeve and Grafting API in other BitNodes. Each level of this Source-File also grants you a Sleeve.
### BitNode 11: The Big Crash
The 2050s was defined by the massive amounts of violent civil unrest and anarchic rebellion that rose all around the world. It was this period of disorder that eventually led to the governmental reformation of many global superpowers, most notably the USA and China. But just as the world was slowly beginning to recover from these dark times, financial catastrophes hit.
In many countries, the high cost of trying to deal with the civil disorder bankrupted the governments. In all of this chaos and confusion, hackers were able to steal billions of dollars from the world's largest electronic banks, prompting an international banking crisis as governments were unable to bail out insolvent banks. Now, the world is slowly crumbling in the middle of the biggest economic crisis of all time.
Destroying this BitNode will give you Source-File 11, or if you already have this Source-File, it will upgrade its level up to a maximum of 3. This Source-File makes it so that company favor increases BOTH the player's salary and reputation gain rate at that company by 1% per favor (rather than just the reputation gain). This Source-File also increases the player's company salary and reputation gain multipliers by:
- Level 1: 32%
- Level 2: 48%
- Level 3: 56%
It also reduces the price increase for every augmentation bought by:
- Level 1: 4%
- Level 2: 6%
- Level 3: 7%
### BitNode 12: The Recursion
To iterate is human; to recurse, divine.
Every time this BitNode is destroyed, it becomes slightly harder. Destroying this BitNode will give you Source-File 12, or if you already have this Source-File, it will upgrade its level. There is no maximum level for Source-File 12. Each level of Source-File 12 lets you start any BitNodes with NeuroFlux Governor equal to the level of this source file.
### BitNode 13: They're lunatics
With the invention of augmentations in the 2040s, a religious group known as the Church of the Machine God has rallied far more support than anyone would have hoped.
Their leader, Allison "Mother" Stanek is said to have created her own augmentation whose power goes beyond any other. Find her in Chongqing and gain her trust.
Destroying this BitNode will give you Source-File 13, or if you already have this Source-File, it will upgrade its level up to a maximum of 3. This Source-File lets the Church of the Machine God appear in other BitNodes.
Each level of this Source-File increases the size of Stanek's Gift.
### BitNode 14: IPvGO Subnet Takeover
In late 2070, the .org bubble burst, and most of the newly-implemented IPvGO 'net collapsed overnight. Since then, various factions have been fighting over small subnets to control their computational power. These subnets are very valuable in the right hands, if you can wrest them from their current owners. You will be opposed by the other factions, but you can overcome them with careful choices. Prevent their attempts to destroy your networks by controlling the open space in the 'net!
Destroying this BitNode will give you Source-File 14, or if you already have this Source-File, it will upgrade its level up to a maximum of 3. This Source-File grants the following benefits:
- Level 1: 100% increased stat multipliers from Node Power
- Level 2: Permanently unlocks the go.cheat API
- Level 3: 25% additive increased success rate for the go.cheat API
This Source-File also increases the maximum favor you can gain for each faction from IPvGO to:
- Level 1: 80
- Level 2: 100
- Level 3: 120

View File

@@ -1,5 +1,74 @@
# Changelog
## v2.6.2 Release: 3 July 2024
### CHANGES
- Hotfix (also backported to 2.6.1): Fixed an issue with invalid format on steam cloud save (@catloversg)
- Augmentations: Augmentations that affect starting money now add money instead of replacing it (@jjclark1982)
- API: ns.spawn can be used with 0 delay for a synchronous effect (@d0sboots)
- API: Added the ns.ramOverride function, which allows altering the ram usage of a script at runtime (@d0sboots)
- Coding Contracts: Improved the performance of the All Valid Math Expressions contract checker (@yichizhng)
- Coding Contracts: Simplified the Shortest Path contract checker (@gmcew)
- Documentation: Various improvements (@mrsimo, @catloversg, @ficocelliguy, @gmcew, @otac0n)
- Electron: Game can now load correctly when the path includes symbolic links (@catloversg)
- Faction: Fixed some edge cases around Favor overflow (@catloversg)
- Faction: All available invites are sent at once (@catloversg)
- Faction UI: show which skills are relevant for each type of Faction work (@gmcew)
- Font: Embedded the JetBrains Mono font as "JetBrainsMono" (@catloversg)
- Go: Can now play manually as white against your own scripts (@ficocelliguy)
- Go: Save a full game history to prevent repeat moves (@ficocelliguy)
- Go: Support offline time / bonus cycles to allow less time between AI moved (@ficocelliguy)
- Hacking: Clamp hack success chance to prevent issues with infinity (@Caldwell-74)
- Hacknet: Fix an issue that caused inaccurate level base cost (@JamesWilcox-git)
- Hacknet: UI improvements (@jjclark1982)
- Hospital: Can now be hospitalized even when busy performing a work task (@catloversg)
- Infiltration: Automating infiltration is now more difficult (@catloversg)
- Infiltration: Wire game shows wire colors on wires (@muesli4brekkies)
- Misc: Changed how negative changes in system time are handled (@catloversg)
- Programs UI: Now displays time remaining (@TheAimMan)
- Servers: Existing servers can now have more than 1 core (@TheAimMan)
- Scripts: Relative imports can now be used (@Caldwell-74)
- Script Editor: Improved detection of possible infinite loops (@G4mingJon4s)
- Script Editor: Cursor location is remembered when switching tabs or game pages (@catloversg)
- Script Editor: Improvements to vim mode (@G4mingJon4s)
- Script Editor: Individual script tabs can be in separate editor modes (@G4mingJon4s)
- Skills: Fix an inconsistency in experience needed to level a skill from 1 to 2 (@catloversg)
- Terminal: Add more options to the rm command (@G4mingJon4s, @d0sboots)
- Terminal: Added grep command (@muesli4brekkies)
- Terminal: Improved autocompletion for mixed case strings (@yichizhng)
- Codebase: Partial migration away from outdated mui/styles (@Caldwell-74)
- Codebase: Cleanup / refactoring (@catloversg, @Caldwell-74, @Snarling, @ficocelliguy, @tomprince)
### SPOILER CHANGES
- Bladeburner: Added a button to stop the current action (@Kelenius, @catloversg)
- Bladeburner: Improved logging of information in the Bladeburner console (@Kelenius, @catloversg)
- Bladeburner: Black Operations show in the expected order again (@catloversg)
- Bitnode 5: Show bitnode multipliers while in BN5.1 (@catloversg)
- Bitverse: Spawned scripts will no longer launch from the bitverse screen (@catloversg)
- Corporation: Refactor markup multiplier (@catloversg)
- Corporation: Allow mass discarding products by selling for 0 (@gmcew)
- Corporation: Allow access to constants even when API access is not available (@ilkecan)
- Gang: Show equipment even when it cannot be purchased yet (@catloversg)
- Gang: Fix an issue with wanted gain rate (@catloversg)
- Gang: Show effective gain rates when bonus time in effect (@G4mingJon4s)
- Grafting: Fixed a spacing issue (@Sphyxis)
- Grafting: Fixed an issue that could cause hacknet node production to be inaccurrate when combined with Grafting (@catloversg)
- Grafting: Fixed an issue that could cause inaccurate HP after Grafting (@catloversg)
- Grafting: Added ns.grafting.waitForOngoingGrafting (@catloversg)
- Intelligence: Changed scaling for intelligence gain from manual hacking (@catloversg)
- Sleeve: Sleeve travel can no longer be performed if the player has insufficient funds (@gmcew)
- Sleeve: It's no longer possible to install an unavailable augmentation on a sleeve (@yichizhng)
- Sleeve: No longer show a dialog message if a sleeve is working at a job while quitting that company (@Kelenius)
- Sleeve: ns.sleeve.setToBladeburnerAction works again for setting sleeves to Bladeburner contract work (@Sphyxis)
- Singularity: Add ns.singularity.getFactionWorkTypes (@catloversg)
- Singularity: Fix an edge case issue with ns.singularity.getAugmentationFactions (@catloversg)
### OTHER
- Nerf noodle bar
## v2.6.1 - 21 May 2024
### MAJOR CHANGES

View File

@@ -27,19 +27,19 @@
## Advanced Mechanics
- [Hacking Algorithms](programming/hackingalgorithms.md)
- [IPvGO](programming/go_algorithms.md)
- [BitNodes](advanced/bitnodes.md)
- [BladeBurners](advanced/bladeburners.md)
- [Corporations](advanced/corporations.md)
- [Gang](advanced/gang.md)
- [Grafting](advanced/grafting.md)
- [Hacknet Servers](advanced/hacknetservers.md)
- [Intelligence](advanced/intelligence.md)
- [List of Factions and their Requirements](advanced/faction_list.md)
- [Offline Scripts and Bonus Time](advanced/offlineandbonustime.md)
- [Sleeves](advanced/sleeves.md)
- [BitNodes](advanced/bitnodes.md)
- [Source-Files](advanced/sourcefiles.md)
- [Gang](advanced/gang.md)
- [Corporations](advanced/corporations.md)
- [Intelligence](advanced/intelligence.md)
- [BladeBurners](advanced/bladeburners.md)
- [Hacknet Servers](advanced/hacknetservers.md)
- [Sleeves](advanced/sleeves.md)
- [Grafting](advanced/grafting.md)
- [Stanek's Gift](advanced/stanek.md)
- [IPvGO](programming/go_algorithms.md)
## Resources

View File

@@ -91,14 +91,12 @@ Now that a simple move type is available, it can be used to play on the current
`await ns.go.passTurn()` can be used if no moves are found. This will end the game if the AI also passes (or just passed previously).
Both `makeMove()` and `passTurn()` , when awaited, return an object that tells you if your move was valid and successfully played, and what the AI's response is.
Both `makeMove()` and `passTurn()`, when awaited, return an object that tells you what the AI's response is, and if the game is over.
```js
{
// If your move was successfully applied to the subnet
success: boolean;
// If the opponent moved or passed, or if the game is now over, or if your move was invalid
type: "invalid" | "move" | "pass" | "gameOver";
// If the opponent moved or passed, or if the game is now over.
type: "move" | "pass" | "gameOver";
x: number | null; // Opponent move's x coord (if applicable)
y: number | null; // Opponent move's y coord (if applicable)
}

View File

@@ -1,12 +1,15 @@
import React, { useState } from "react";
import React, { useLayoutEffect, useState } from "react";
import Button from "@mui/material/Button";
import { MD } from "../../ui/MD/MD";
import { getPage } from "../root";
import { Navigator, useHistory } from "../../ui/React/Documentation";
import { Navigator, windowTopPositionOfPages, useHistory } from "../../ui/React/Documentation";
import { CONSTANTS } from "../../Constants";
import { asFilePath, resolveFilePath } from "../../Paths/FilePath";
import Box from "@mui/material/Box";
import { Settings } from "../../Settings/Settings";
import { Router } from "../../ui/GameRoot";
import { Page } from "../../ui/Router";
export function DocumentationRoot({ docPage }: { docPage?: string }): React.ReactElement {
const history = useHistory();
@@ -15,7 +18,6 @@ export function DocumentationRoot({ docPage }: { docPage?: string }): React.Reac
history.push(asFilePath(deepLink));
setDeepLink(undefined);
}
const page = getPage(history.page);
const navigator = {
navigate(relPath: string, external: boolean) {
const newPath = resolveFilePath("./" + relPath, history.page);
@@ -30,19 +32,30 @@ export function DocumentationRoot({ docPage }: { docPage?: string }): React.Reac
return;
}
history.push(newPath);
// Reset scroll to the top of the page.
window.scrollTo(0, 0);
},
};
// We need to use "useLayoutEffect" instead of "useEffect". "useLayoutEffect" is fired before the browser repaints the
// screen.
useLayoutEffect(() => {
return () => {
if (Router.page() !== Page.Documentation) {
windowTopPositionOfPages.set(history.page, window.scrollY);
}
};
}, [history]);
return (
<>
<Button onClick={() => history.pop()}>Back</Button>
<Button onClick={() => history.home()}>Home</Button>
<Navigator.Provider value={navigator}>
<MD md={page + ""} />
</Navigator.Provider>
<Box position="fixed" top={0} zIndex={1} width="100%" paddingTop="8px" bgcolor={Settings.theme.backgroundprimary}>
<Button onClick={() => history.pop()}>Back</Button>
<Button onClick={() => history.home()}>Home</Button>
</Box>
<Box paddingTop="50px">
<Navigator.Provider value={navigator}>
<MD pageFilePath={history.page} top={windowTopPositionOfPages.get(history.page) ?? 0} />
</Navigator.Provider>
</Box>
</>
);
}

View File

@@ -14,4 +14,3 @@ export * from "./Programs/Enums";
export * from "./StockMarket/Enums";
export * from "./ui/Enums";
export * from "./Work/Enums";
export * from "./Myrian/Enums";

View File

@@ -657,8 +657,8 @@ export const FactionInfos: Record<FactionName, FactionInfo> = {
// Early game factions - factions the player will prestige with early on that don't belong in other categories.
[FactionName.Netburners]: new FactionInfo({
infoText: <>{"~~//*>H4CK|\\|3T 8URN3R5**>?>\\~~"}</>,
rumorText: <>{"~~//*>H4CK|\\|3T 8URN3R5**>?>\\~~"}</>,
infoText: <>{"~~//*>H4CK|\\|3T 8URN3R5**>?>\\\\~~"}</>,
rumorText: <>{"~~//*>H4CK|\\|3T 8URN3R5**>?>\\\\~~"}</>,
inviteReqs: [haveSkill("hacking", 80), totalHacknetRam(8), totalHacknetCores(4), totalHacknetLevels(100)],
rumorReqs: [totalHacknetLevels(50)],
offerHackingWork: true,

View File

@@ -42,6 +42,18 @@ export const MiscPage = (): React.ReactElement => {
</>
}
/>
<OptionSwitch
checked={Settings.MonacoDefaultToVim}
onChange={(newValue) => (Settings.MonacoDefaultToVim = newValue)}
text="Enable Vim as default editor"
tooltip={
<>
This setting is only used when opening a file through ways that do not determine the editor mode. Using
'nano' or 'vim' will set the editor mode for the specified files, while 'ls' will open the file using the
the value from this setting.
</>
}
/>
</GameOptionsPage>
);
};

View File

@@ -154,9 +154,11 @@ export class Gang {
const newWanted = oldWanted + wantedLevelGainPerCycle * numCycles;
// Allows recovery when wanted / respect ratio is too high
this.wanted = newWanted * (1 - justice * 0.001);
this.wantedGainRate -= newWanted - this.wanted;
this.wantedGainRate = (this.wanted - oldWanted) / numCycles;
// Prevent overflow
if (this.wanted < 1 || (wantedLevelGainPerCycle <= 0 && this.wanted > oldWanted)) this.wanted = 1;
if (this.wanted < 1 || (wantedLevelGainPerCycle <= 0 && this.wanted > oldWanted)) {
this.wanted = 1;
}
}
Player.gainMoney(moneyGainPerCycle * numCycles, "gang");
}
@@ -315,10 +317,14 @@ export class Gang {
}
getRecruitsAvailable(): number {
if (this.members.length >= GangConstants.MaximumGangMembers) {
return 0;
}
const numFreeMembers = 3;
const recruitCostBase = 5;
if (this.members.length < numFreeMembers && this.respect < Math.pow(recruitCostBase, numFreeMembers))
if (this.members.length < numFreeMembers && this.respect < Math.pow(recruitCostBase, numFreeMembers)) {
return numFreeMembers - this.members.length; // if the max possible is less than freeMembers
}
return Math.floor(Math.log(this.respect) / Math.log(recruitCostBase)) + numFreeMembers - this.members.length; //else
}

View File

@@ -21,31 +21,6 @@ import { Settings } from "../../Settings/Settings";
import { StatsRow } from "../../ui/React/StatsRow";
import { useRerender } from "../../ui/React/hooks";
interface INextRevealProps {
upgrades: string[];
type: UpgradeType;
}
function NextReveal(props: INextRevealProps): React.ReactElement {
const gang = useGang();
const upgrades = Object.keys(GangMemberUpgrades)
.filter((upgName: string) => {
const upg = GangMemberUpgrades[upgName];
if (Player.money > gang.getUpgradeCost(upg)) return false;
if (upg.type !== props.type) return false;
if (props.upgrades.includes(upgName)) return false;
return true;
})
.map((upgName: string) => GangMemberUpgrades[upgName]);
if (upgrades.length === 0) return <></>;
return (
<Typography>
Next at <Money money={gang.getUpgradeCost(upgrades[0])} />
</Typography>
);
}
function PurchasedUpgrade({ upgName }: { upgName: string }): React.ReactElement {
const upg = GangMemberUpgrades[upgName];
return (
@@ -65,6 +40,8 @@ interface IUpgradeButtonProps {
function UpgradeButton(props: IUpgradeButtonProps): React.ReactElement {
const gang = useGang();
const upgradeCost = gang.getUpgradeCost(props.upg);
const isUpgradable = Player.money >= upgradeCost;
function onClick(): void {
props.member.buyUpgrade(props.upg);
props.rerender();
@@ -72,9 +49,13 @@ function UpgradeButton(props: IUpgradeButtonProps): React.ReactElement {
return (
<Tooltip title={<Typography dangerouslySetInnerHTML={{ __html: props.upg.desc }} />}>
<span>
<Button onClick={onClick} sx={{ display: "flex", flexDirection: "column", width: "100%", height: "100%" }}>
<Button
disabled={!isUpgradable}
onClick={onClick}
sx={{ display: "flex", flexDirection: "column", width: "100%", height: "100%" }}
>
<Typography sx={{ display: "block" }}>{props.upg.name}</Typography>
<Money money={gang.getUpgradeCost(props.upg)} />
<Money money={upgradeCost} forPurchase />
</Button>
</span>
</Tooltip>
@@ -86,7 +67,6 @@ interface IPanelProps {
}
function GangMemberUpgradePanel(props: IPanelProps): React.ReactElement {
const gang = useGang();
const rerender = useRerender();
const [currentCategory, setCurrentCategory] = useState("Weapons");
@@ -94,7 +74,6 @@ function GangMemberUpgradePanel(props: IPanelProps): React.ReactElement {
return Object.keys(GangMemberUpgrades)
.filter((upgName: string) => {
const upg = GangMemberUpgrades[upgName];
if (Player.money < gang.getUpgradeCost(upg)) return false;
if (upg.type !== type) return false;
if (list.includes(upgName)) return false;
return true;
@@ -214,7 +193,6 @@ function GangMemberUpgradePanel(props: IPanelProps): React.ReactElement {
<UpgradeButton key={upg.name} rerender={rerender} member={props.member} upg={upg} />
))}
</Box>
<NextReveal type={categories[currentCategory][1] as UpgradeType} upgrades={props.member.upgrades} />
</Box>
</span>
</Box>

View File

@@ -27,6 +27,21 @@ export function GangStats(): React.ReactElement {
} else {
territoryStr = formatNumberNoSuffix(territoryMult, 2);
}
const hasEnoughBonusTime = gang.storedCycles > GangConstants.maxCyclesToProcess;
const bonusCyclesInOneSecond = 5 * GangConstants.maxCyclesToProcess;
const respectGainRateInBonusTime = hasEnoughBonusTime
? `[Effective Gain: ${formatRespect(gang.respectGainRate * bonusCyclesInOneSecond)} / sec]`
: "";
const wantedGainRateInBonusTime = hasEnoughBonusTime
? `[Effective Gain: ${formatWanted(gang.wantedGainRate * bonusCyclesInOneSecond)} / sec]`
: "";
const moneyGainRateInBonusTime = hasEnoughBonusTime ? (
<>
[Effective Gain: <MoneyRate money={gang.moneyGainRate * bonusCyclesInOneSecond} />]
</>
) : (
""
);
return (
<>
@@ -42,9 +57,7 @@ export function GangStats(): React.ReactElement {
>
<Typography>
Respect: {formatRespect(gang.respect)} ({formatRespect(5 * gang.respectGainRate)} / sec){" "}
{gang.storedCycles > 2 * GangConstants.maxCyclesToProcess
? `[Effective Gain: ${formatRespect(5 * gang.respectGainRate * GangConstants.maxCyclesToProcess)} / sec]`
: ""}
{respectGainRateInBonusTime}
</Typography>
</Tooltip>
</Box>
@@ -60,9 +73,7 @@ export function GangStats(): React.ReactElement {
>
<Typography>
Wanted Level: {formatWanted(gang.wanted)} ({formatWanted(5 * gang.wantedGainRate)} / sec){" "}
{gang.storedCycles > 2 * GangConstants.maxCyclesToProcess
? `[Effective Gain: ${formatWanted(5 * gang.wantedGainRate * GangConstants.maxCyclesToProcess)} / sec]`
: ""}
{wantedGainRateInBonusTime}
</Typography>
</Tooltip>
</Box>
@@ -76,14 +87,7 @@ export function GangStats(): React.ReactElement {
</Box>
<Typography>
Money gain rate: <MoneyRate money={5 * gang.moneyGainRate} />{" "}
{gang.storedCycles > 2 * GangConstants.maxCyclesToProcess ? "[Effective Gain:" : ""}{" "}
{gang.storedCycles > 2 * GangConstants.maxCyclesToProcess ? (
<MoneyRate money={5 * gang.moneyGainRate * GangConstants.maxCyclesToProcess} />
) : (
""
)}
{gang.storedCycles > 2 * GangConstants.maxCyclesToProcess ? "]" : ""}
Money gain rate: <MoneyRate money={5 * gang.moneyGainRate} /> {moneyGainRateInBonusTime}
</Typography>
<Box display="flex">

View File

@@ -22,7 +22,7 @@ export function calculateLevelUpgradeCost(startingLevel: number, extraLevels = 1
const mult = HacknetNodeConstants.UpgradeLevelMult;
let totalMultiplier = 0;
let currLevel = startingLevel;
let currLevel = startingLevel - 1;
for (let i = 0; i < sanitizedLevels; ++i) {
totalMultiplier += Math.pow(mult, currLevel);
++currLevel;

View File

@@ -17,6 +17,7 @@ export function GeneralInfo(props: IProps): React.ReactElement {
The Hacknet is a global, decentralized network of machines. It is used by hackers all around the world to
anonymously share computing power and perform distributed cyberattacks without the fear of being traced.
</Typography>
<br />
{!props.hasHacknetServers ? (
<>
<Typography>

View File

@@ -164,7 +164,7 @@ export function HacknetNodeElem(props: IProps): React.ReactElement {
return (
<Grid item component={Paper} p={1}>
<Table size="small">
<Table size="small" sx={{ whiteSpace: "nowrap" }}>
<TableBody>
<TableRow>
<TableCell colSpan={3}>

View File

@@ -102,21 +102,28 @@ export function HacknetRoot(): React.ReactElement {
<Typography variant="h4">Hacknet {hasHacknetServers() ? "Servers" : "Nodes"}</Typography>
<GeneralInfo hasHacknetServers={hasHacknetServers()} />
<PurchaseButton cost={purchaseCost} multiplier={purchaseMultiplier} onClick={handlePurchaseButtonClick} />
<br />
<PlayerInfo totalProduction={totalProduction} />
<br />
{hasHacknetServers() && (
<>
<Button onClick={() => setOpen(true)}>Spend Hashes on Upgrades</Button>
<br />
</>
)}
<Grid container spacing={2}>
<Grid item xs={6}>
<PlayerInfo totalProduction={totalProduction} />
<PurchaseButton cost={purchaseCost} multiplier={purchaseMultiplier} onClick={handlePurchaseButtonClick} />
</Grid>
<Grid item xs={6}>
<MultiplierButtons onClicks={purchaseMultiplierOnClicks} purchaseMultiplier={purchaseMultiplier} />
</Grid>
</Grid>
{hasHacknetServers() && <Button onClick={() => setOpen(true)}>Spend Hashes on Upgrades</Button>}
<Box sx={{ display: "grid", width: "100%", gridTemplateColumns: "repeat(auto-fit, 30em)" }}>{nodes}</Box>
<HashUpgradeModal open={open} onClose={() => setOpen(false)} />
</>

View File

@@ -245,10 +245,10 @@ export function HacknetServerElem(props: IProps): React.ReactElement {
return (
<Grid item component={Paper} p={1}>
<Table size="small">
<Table size="small" sx={{ whiteSpace: "nowrap" }}>
<TableBody>
<TableRow>
<TableCell>
<TableCell colSpan={3}>
<Typography>{node.hostname}</Typography>
</TableCell>
</TableRow>

View File

@@ -12,7 +12,9 @@ import { Money } from "../../ui/React/Money";
import { MoneyRate } from "../../ui/React/MoneyRate";
import { HashRate } from "../../ui/React/HashRate";
import { Hashes } from "../../ui/React/Hashes";
import Typography from "@mui/material/Typography";
import { Paper, Typography } from "@mui/material";
import { StatsTable } from "../../ui/React/StatsTable";
import { Tooltip } from "@mui/material";
interface IProps {
totalProduction: number;
@@ -21,31 +23,39 @@ interface IProps {
export function PlayerInfo(props: IProps): React.ReactElement {
const hasServers = hasHacknetServers();
let prod;
const rows: React.ReactNode[][] = [];
rows.push(["Money Spent:", <Money key="money" money={-Player.moneySourceA.hacknet_expenses || 0} />]);
rows.push(["Money Produced:", <Money key="money" money={Player.moneySourceA.hacknet} />]);
if (hasServers) {
prod = <HashRate hashes={props.totalProduction} />;
rows.push([
"Hashes:",
<span key={"hashes"}>
<Hashes hashes={Player.hashManager.hashes} /> / <Hashes hashes={Player.hashManager.capacity} />
</span>,
]);
rows.push([
"Hash Rate:",
<Tooltip
key="moneyRate"
title={
<Typography>
<MoneyRate money={(props.totalProduction * 1e6) / 4} /> if sold for money
</Typography>
}
>
<span>
<HashRate key="hashRate" hashes={props.totalProduction} />
</span>
</Tooltip>,
]);
} else {
prod = <MoneyRate money={props.totalProduction} />;
rows.push(["Production Rate:", <MoneyRate key="moneyRate" money={props.totalProduction} />]);
}
return (
<>
<Typography>
Money:
<Money money={Player.money} />
</Typography>
{hasServers && (
<>
<Typography>
Hashes: <Hashes hashes={Player.hashManager.hashes} /> / <Hashes hashes={Player.hashManager.capacity} />
</Typography>
</>
)}
<Typography>
Total Hacknet {hasServers ? "Server" : "Node"} Production: {prod}
</Typography>
</>
<Paper sx={{ display: "inline-block", padding: "0.5em 1em", margin: "0.5em 0" }}>
<Typography variant="h6">Hacknet Summary</Typography>
<StatsTable rows={rows} />
</Paper>
);
}

View File

@@ -5,15 +5,23 @@ interface IProps {
onFinish: () => void;
}
export function Countdown(props: IProps): React.ReactElement {
export function Countdown({ onFinish }: IProps): React.ReactElement {
const [x, setX] = useState(3);
useEffect(() => {
if (x === 0) {
props.onFinish();
return;
onFinish();
}
setTimeout(() => setX(x - 1), 300);
});
}, [x, onFinish]);
useEffect(() => {
const id = setInterval(() => {
setX((previousValue) => previousValue - 1);
}, 300);
return () => {
clearInterval(id);
};
}, []);
return (
<Paper sx={{ p: 1, textAlign: "center" }}>

View File

@@ -1,6 +1,6 @@
import { Button, Container, Paper, Typography } from "@mui/material";
import React, { useCallback, useState } from "react";
import { FactionName } from "@enums";
import { FactionName, ToastVariant } from "@enums";
import { Router } from "../../ui/GameRoot";
import { Page } from "../../ui/Router";
import { Player } from "@player";
@@ -15,6 +15,7 @@ import { SlashGame } from "./SlashGame";
import { Victory } from "./Victory";
import { WireCuttingGame } from "./WireCuttingGame";
import { calculateDamageAfterFailingInfiltration } from "../utils";
import { SnackbarEvents } from "../../ui/React/Snackbar";
type GameProps = {
StartingDifficulty: number;
@@ -91,11 +92,18 @@ export function Game(props: GameProps): React.ReactElement {
setStage(Stage.Countdown);
pushResult(false);
Player.receiveRumor(FactionName.ShadowsOfAnarchy);
// Kill the player immediately if they use automation, so
// it's clear they're not meant to
const damage = options?.automated
? Player.hp.current
: calculateDamageAfterFailingInfiltration(props.StartingDifficulty);
let damage = calculateDamageAfterFailingInfiltration(props.StartingDifficulty);
// Kill the player immediately if they use automation, so it's clear they're not meant to
if (options?.automated) {
damage = Player.hp.current;
setTimeout(() => {
SnackbarEvents.emit(
"You were hospitalized. Do not try to automate infiltration!",
ToastVariant.WARNING,
5000,
);
}, 500);
}
if (Player.takeDamage(damage)) {
Router.toPage(Page.City);
return;

View File

@@ -23,10 +23,15 @@ export function GameTimer({
const totalMillis =
(!ignoreAugment_WKSharmonizer && Player.hasAugmentation(AugmentationName.WKSharmonizer, true) ? 1.3 : 1) * millis;
useEffect(() => {
if (v <= 0) {
onExpire();
}
}, [v, onExpire]);
useEffect(() => {
const intervalId = setInterval(() => {
setV((old) => {
if (old <= 0) onExpire();
return old - (tick / totalMillis) * 100;
});
}, tick);
@@ -40,10 +45,10 @@ export function GameTimer({
// TODO(hydroflame): there's like a bug where it triggers the end before the
// bar physically reaches the end
return noPaper ? (
<ProgressBar variant="determinate" value={v} color="primary" />
<ProgressBar variant="determinate" value={Math.max(v, 0)} color="primary" />
) : (
<Paper sx={{ p: 1, mb: 1 }}>
<ProgressBar variant="determinate" value={v} color="primary" />
<ProgressBar variant="determinate" value={Math.max(v, 0)} color="primary" />
</Paper>
);
}

View File

@@ -1,16 +1,18 @@
import React, { useEffect } from "react";
interface IProps {
onKeyDown: (this: Document, event: KeyboardEvent) => void;
onKeyDown: (event: KeyboardEvent) => void;
onFailure: (options?: { automated: boolean }) => void;
}
export function KeyHandler(props: IProps): React.ReactElement {
useEffect(() => {
function press(this: Document, event: KeyboardEvent): void {
if (!event.isTrusted) return;
const f = props.onKeyDown.bind(this);
f(event);
function press(event: KeyboardEvent): void {
if (!event.isTrusted || !(event instanceof KeyboardEvent)) {
props.onFailure({ automated: true });
return;
}
props.onKeyDown(event);
}
document.addEventListener("keydown", press);
return () => document.removeEventListener("keydown", press);

View File

@@ -1,5 +1,5 @@
import { Box, Paper, Typography } from "@mui/material";
import React, { useEffect, useState } from "react";
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { AugmentationName } from "@enums";
import { Player } from "@player";
import { KEY } from "../../utils/helpers/keyCodes";
@@ -27,34 +27,53 @@ const difficulties: {
export function SlashGame({ difficulty, onSuccess, onFailure }: IMinigameProps): React.ReactElement {
const [phase, setPhase] = useState(0);
const [hasAugment, setHasAugment] = useState(false);
const [guardingTime, setGuardingTime] = useState(0);
const timeOutId = useRef<number | ReturnType<typeof setTimeout>>(-1);
const hasWKSharmonizer = Player.hasAugmentation(AugmentationName.WKSharmonizer, true);
const hasMightOfAres = Player.hasAugmentation(AugmentationName.MightOfAres, true);
useEffect(() => {
// Determine timeframes for game phase changes
const data = useMemo(() => {
// Determine time window of phases
const newDifficulty: Difficulty = { window: 0 };
interpolate(difficulties, difficulty, newDifficulty);
const distractedTime =
newDifficulty.window * (Player.hasAugmentation(AugmentationName.WKSharmonizer, true) ? 1.3 : 1);
const distractedTime = newDifficulty.window * (hasWKSharmonizer ? 1.3 : 1);
const alertedTime = 250;
const guardingTime = Math.random() * 3250 + 1500 - (distractedTime + alertedTime);
// Set initial game state
setPhase(0);
setGuardingTime(guardingTime);
setHasAugment(Player.hasAugmentation(AugmentationName.MightOfAres, true));
return {
hasAugment: hasMightOfAres,
guardingTime,
distractedTime,
alertedTime,
};
}, [difficulty, hasWKSharmonizer, hasMightOfAres]);
// Setup timer for game phases
let id = setTimeout(() => {
useEffect(() => {
return () => {
if (timeOutId.current !== -1) {
clearTimeout(timeOutId.current);
}
};
}, []);
const startPhase1 = useCallback(
(alertedTime: number, distractedTime: number) => {
setPhase(1);
id = setTimeout(() => {
timeOutId.current = setTimeout(() => {
setPhase(2);
id = setTimeout(() => onFailure(), alertedTime);
timeOutId.current = setTimeout(() => onFailure(), alertedTime);
}, distractedTime);
}, guardingTime);
},
[onFailure],
);
return () => clearTimeout(id);
}, [difficulty, onSuccess, onFailure]);
useEffect(() => {
// Start the timer if the player does not have MightOfAres augmentation.
if (phase === 0 && !data.hasAugment) {
timeOutId.current = setTimeout(() => {
startPhase1(data.alertedTime, data.distractedTime);
}, data.guardingTime);
}
}, [phase, data, startPhase1]);
function press(this: Document, event: KeyboardEvent): void {
event.preventDefault();
@@ -76,10 +95,18 @@ export function SlashGame({ difficulty, onSuccess, onFailure }: IMinigameProps):
Do not alert him!
</Typography>
<br />
{hasAugment && (
{phase === 0 && data.hasAugment && (
<Box sx={{ my: 1 }}>
<Typography variant="h5">The sentinel will drop his guard and be distracted in ...</Typography>
<GameTimer millis={guardingTime} onExpire={() => null} ignoreAugment_WKSharmonizer noPaper tick={20} />
<GameTimer
millis={data.guardingTime}
onExpire={() => {
startPhase1(data.alertedTime, data.distractedTime);
}}
ignoreAugment_WKSharmonizer
noPaper
tick={20}
/>
<br />
</Box>
)}

View File

@@ -80,7 +80,7 @@ export enum LocationType {
Gym = "Gym",
Hospital = "Hospital",
Slums = "Slums",
Special = "Special", // This location has special options/activities (e.g. Bladeburner, Re-sleeving)
Special = "Special", // This location has special options/activities (e.g., Bladeburner, Grafting, etc.)
StockMarket = "Stock Market",
TechVendor = "Tech Vendor",
TravelAgency = "Travel Agency",

View File

@@ -4,7 +4,7 @@
*
* Examples:
* - Bladeburner @ NSA
* - Re-sleeving @ VitaLife
* - Grafting @ VitaLife
* - Create Corporation @ City Hall
*
* This subcomponent creates all of the buttons for interacting with those special
@@ -69,7 +69,7 @@ export function SpecialLocation(props: SpecialLocationProps): React.ReactElement
}
}
/** Click handler for Resleeving button at New Tokyo VitaLife */
/** Click handler for Secret lab button at New Tokyo VitaLife */
function handleGrafting(): void {
Router.toPage(Page.Grafting);
}
@@ -300,36 +300,11 @@ export function SpecialLocation(props: SpecialLocationProps): React.ReactElement
}
function renderGlitch(): React.ReactElement {
const onClick = () => {
dialogBoxCreate(
"Hexabytes of information are streamed to your mind. Completely drained one thing remained clear as crystal. You now understand how to connect directly to the machine running the bitnodes. Myrian.",
);
Router.toPage(Page.MyrianOS);
Player.myrianConnection = true;
};
if (!Player.canAccessMyrian())
return (
<>
<Typography>
<CorruptableText
content={"An eerie aura surrounds this area. You feel you should leave."}
spoiler={false}
/>
</Typography>
</>
);
return (
<>
<Typography>
You find yourself standing in a small, unremarkable alley. Despite the lack of air you do not feel the need to
breathe. There is no light, yet you can see every details of every objects in the alley. A rat walking in the
alley completely stop in it's track as if frozen in time. You know what this means. This location has fallen
through the crack of the Enders.
<br />
In the middle of the alley is a personal link port. You can connect your personal link to the anomaly.
<CorruptableText content={"An eerie aura surrounds this area. You feel you should leave."} spoiler={false} />
</Typography>
<Button onClick={onClick}>Connect your personal link to the anomaly</Button>
</>
);
}

View File

@@ -1,100 +0,0 @@
export enum DeviceType {
Bus = "bus",
ISocket = "isocket",
OSocket = "osocket",
Reducer = "reducer",
Cache = "cache",
Lock = "lock",
Battery = "battery",
}
export enum Component {
// tier 0
R0 = "r0",
G0 = "g0",
B0 = "b0",
// tier 1
R1 = "r1",
G1 = "g1",
B1 = "b1",
Y1 = "y1",
C1 = "c1",
M1 = "m1",
// tier 2
R2 = "r2",
G2 = "g2",
B2 = "b2",
Y2 = "y2",
C2 = "c2",
M2 = "m2",
W2 = "w2",
// tier 3
R3 = "r3",
G3 = "g3",
B3 = "b3",
Y3 = "y3",
C3 = "c3",
M3 = "m3",
W3 = "w3",
// tier 4
R4 = "r4",
G4 = "g4",
B4 = "b4",
Y4 = "y4",
C4 = "c4",
M4 = "m4",
W4 = "w4",
// tier 5
R5 = "r5",
G5 = "g5",
B5 = "b5",
Y5 = "y5",
C5 = "c5",
M5 = "m5",
W5 = "w5",
// tier 6
Y6 = "y6",
C6 = "c6",
M6 = "m6",
W6 = "w6",
// tier 7
W7 = "w7",
}
export enum Glitch {
// Locks spawn at random
Segmentation = "segmentation",
// ISockets and OSockets move around on their own
Roaming = "roaming",
// OSocket ask for more complicated components
Encryption = "encryption",
// Energy starts being consumed (level 0 is no consumption)
Magnetism = "magnetism",
// Hidden tiles on the board, when stepped on the bus loses upgrades
Rust = "rust",
// Move slows down
Friction = "friction",
// Transfer components and charging slows down
Isolation = "isolation",
// Install/Uninstall slows down
Virtualization = "virtualization",
// Reduce slows down
Jamming = "jamming",
}

View File

@@ -1,87 +0,0 @@
import { Device, DeviceID } from "@nsdefs";
import { DeviceType, Component, Glitch } from "@enums";
import { glitchMult } from "./formulas/glitches";
import { isDeviceISocket, pickOne } from "./utils";
import { componentTiers } from "./formulas/components";
import { NewBus, NewISocket, NewOSocket } from "./NewDevices";
import { startRoaming } from "./glitches/roaming";
import { startRust } from "./glitches/rust";
import { startSegmentation } from "./glitches/segmentation";
import { startBattery } from "./glitches/battery";
export interface Myrian {
vulns: number;
totalVulns: number;
devices: Device[];
glitches: Record<Glitch, number>;
rust: Record<string, boolean>;
}
export const myrianSize = 12;
const defaultGlitches = Object.values(Glitch).reduce((acc, g) => ({ ...acc, [g]: 0 }), {}) as Record<Glitch, number>;
export const myrian: Myrian = {
vulns: 0,
totalVulns: 0,
devices: [],
glitches: { ...defaultGlitches },
rust: {},
};
export const loadMyrian = (save: string) => {
resetMyrian();
startRoaming();
startRust();
startSegmentation();
startBattery();
if (!save) return;
const savedMyrian = JSON.parse(save);
Object.assign(myrian, savedMyrian);
myrian.devices.forEach((d) => (d.isBusy = false));
myrian.devices.filter(isDeviceISocket).forEach((d) => (d.content = new Array(d.maxContent).fill(d.emitting)));
};
export const inMyrianBounds = (x: number, y: number) => x >= 0 && x < myrianSize && y >= 0 && y < myrianSize;
export const findDevice = (id: DeviceID, type?: DeviceType): Device | undefined =>
myrian.devices.find(
(e) => (typeof id === "string" ? e.name === id : e.x === id[0] && e.y === id[1]) && (!type || type === e.type),
);
export const removeDevice = (id: DeviceID, type?: DeviceType) => {
myrian.devices = myrian.devices.filter(
(e) => !((typeof id === "string" ? e.name === id : e.x === id[0] && e.y === id[1]) && (!type || type === e.type)),
);
};
export const getTotalGlitchMult = () =>
Object.entries(myrian.glitches).reduce((acc, [glitch, lvl]) => {
return acc * glitchMult(glitch as Glitch, lvl);
}, 1);
export const getNextOSocketRequest = (tier: number) => {
const potential = componentTiers.slice(0, tier + 1).flat();
return new Array(Math.floor(Math.pow(Math.random() * tier, 0.75) + 1)).fill(null).map(() => pickOne(potential));
};
export const countDevices = (type: DeviceType) =>
myrian.devices.reduce((acc, d) => (d.type === type ? acc + 1 : acc), 0);
export const resetMyrian = () => {
myrian.vulns = 0;
myrian.totalVulns = 0;
myrian.devices = [];
myrian.glitches = { ...defaultGlitches };
myrian.rust = {};
NewBus("alice", Math.floor(myrianSize / 2), Math.floor(myrianSize / 2));
NewISocket("isocket0", Math.floor(myrianSize / 4), 0, Component.R0);
NewISocket("isocket1", Math.floor(myrianSize / 2), 0, Component.G0);
NewISocket("isocket2", Math.floor((myrianSize * 3) / 4), 0, Component.B0);
NewOSocket("osocket0", Math.floor(myrianSize / 4), Math.floor(myrianSize - 1));
NewOSocket("osocket1", Math.floor(myrianSize / 2), Math.floor(myrianSize - 1));
NewOSocket("osocket2", Math.floor((myrianSize * 3) / 4), Math.floor(myrianSize - 1));
};

View File

@@ -1,110 +0,0 @@
import { Battery, Bus, Cache, ISocket, Lock, OSocket, Reducer } from "@nsdefs";
import { Component, DeviceType } from "@enums";
import { myrian } from "./Myrian";
import { getNextOSocketRequest } from "./Myrian";
export const NewBus = (name: string, x: number, y: number) => {
const bus: Bus = {
name,
type: DeviceType.Bus,
isBusy: false,
x,
y,
content: [],
maxContent: 1,
moveLvl: 0,
transferLvl: 0,
reduceLvl: 0,
installLvl: 0,
energy: 16,
maxEnergy: 16,
};
myrian.devices.push(bus);
};
export const NewCache = (name: string, x: number, y: number) => {
const cache: Cache = {
name,
type: DeviceType.Cache,
isBusy: false,
content: [],
maxContent: 1,
x,
y,
};
myrian.devices.push(cache);
return cache;
};
export const NewReducer = (name: string, x: number, y: number) => {
const reducer: Reducer = {
name,
type: DeviceType.Reducer,
isBusy: false,
x,
y,
content: [],
maxContent: 2,
tier: 1,
};
myrian.devices.push(reducer);
return reducer;
};
export const NewISocket = (name: string, x: number, y: number, emitting: Component) => {
const isocket: ISocket = {
name,
type: DeviceType.ISocket,
isBusy: false,
x,
y,
emitting: emitting,
emissionLvl: 0,
cooldownUntil: 0,
content: [emitting],
maxContent: 1,
};
myrian.devices.push(isocket);
};
export const NewOSocket = (name: string, x: number, y: number) => {
const osocket: OSocket = {
name,
type: DeviceType.OSocket,
isBusy: false,
x,
y,
currentRequest: getNextOSocketRequest(0),
content: [],
maxContent: 1,
};
myrian.devices.push(osocket);
return osocket;
};
export const NewLock = (name: string, x: number, y: number) => {
const lock: Lock = {
name,
type: DeviceType.Lock,
isBusy: false,
x,
y,
};
myrian.devices.push(lock);
return lock;
};
export const NewBattery = (name: string, x: number, y: number) => {
const battery: Battery = {
name,
type: DeviceType.Battery,
isBusy: false,
x,
y,
tier: 0,
energy: 64,
maxEnergy: 64,
};
myrian.devices.push(battery);
};

View File

@@ -1,12 +0,0 @@
import { Component } from "@enums";
export const componentTiers = [
[Component.R0, Component.G0, Component.B0],
[Component.R1, Component.G1, Component.B1, Component.Y1, Component.C1, Component.M1],
[Component.R2, Component.G2, Component.B2, Component.Y2, Component.C2, Component.M2, Component.W2],
[Component.R3, Component.G3, Component.B3, Component.Y3, Component.C3, Component.M3, Component.W3],
[Component.R4, Component.G4, Component.B4, Component.Y4, Component.C4, Component.M4, Component.W4],
[Component.R5, Component.G5, Component.B5, Component.Y5, Component.C5, Component.M5, Component.W5],
[Component.Y6, Component.C6, Component.M6, Component.W6],
[Component.W7],
];

View File

@@ -1,67 +0,0 @@
import { DeviceType } from "@enums";
// parameters for a exponential formula, a^(b*X+c)+d
type ExponentialFormulaParams = [number, number, number, number];
// Parameters for a cost that shouldn't be available. Such as upgrading the max energy of a isocket.
const NA: ExponentialFormulaParams = [Infinity, Infinity, Infinity, Infinity];
type DeviceScale = Record<DeviceType, ExponentialFormulaParams>;
// Default scale for each device type, helps simplify code.
const defaultScale = Object.keys(DeviceType).reduce((acc, type) => ({ ...acc, [type]: NA }), {}) as DeviceScale;
// Exponential formula, a^(b*X+c)+d
const exp = (p: ExponentialFormulaParams, x: number): number => Math.pow(p[0], p[1] * x + p[2]) + p[3];
// Wrap exp with a specific scale for each device type.
const makeExpFunction = (p: Partial<DeviceScale>) => {
const scale = { ...defaultScale, ...p };
return (type: DeviceType, x: number) => exp(scale[type], x);
};
export const upgradeMaxContentCost = makeExpFunction({
[DeviceType.Bus]: [8, 0.5, 2, 0],
[DeviceType.ISocket]: [4, 1, 5, 0],
[DeviceType.Reducer]: [256, 1, -3, 512],
[DeviceType.Cache]: [1.2, 10, 0, 63],
});
export const upgradeTierCost = makeExpFunction({
[DeviceType.Reducer]: [1.5, 1, 2, 0],
[DeviceType.Battery]: [2, 1, 3, 0],
});
export const upgradeEmissionCost = makeExpFunction({
[DeviceType.ISocket]: [2, 1, 3, 0],
});
export const upgradeMoveLvlCost = makeExpFunction({
[DeviceType.Bus]: [2, 1, 3, 0],
});
export const upgradeTransferLvlCost = makeExpFunction({
[DeviceType.Bus]: [2, 1, 3, 0],
});
export const upgradeReduceLvlCost = makeExpFunction({
[DeviceType.Bus]: [2, 1, 3, 0],
});
export const upgradeInstallLvlCost = makeExpFunction({
[DeviceType.Bus]: [2, 1, 3, 0],
});
export const upgradeMaxEnergyCost = makeExpFunction({
[DeviceType.Bus]: [1.1, 1, -8, 16],
[DeviceType.Battery]: [1.1, 1, -16, 8],
});
export const installDeviceCost = makeExpFunction({
[DeviceType.Bus]: [4, 0.5, 2, 0],
[DeviceType.ISocket]: [2, 1, 4, 0],
[DeviceType.OSocket]: [4, 1, 3, 0],
[DeviceType.Reducer]: [5, 0.5, 1, 0],
[DeviceType.Cache]: [1.2, 5, 0, 18],
[DeviceType.Battery]: [1.2, 10, 0, 63],
});

View File

@@ -1,46 +0,0 @@
import { Glitch } from "@enums";
export const glitchMaxLvl: Record<Glitch, number> = {
[Glitch.Segmentation]: 10,
[Glitch.Roaming]: 10,
[Glitch.Encryption]: 7,
[Glitch.Magnetism]: 10,
[Glitch.Rust]: 10,
[Glitch.Friction]: 3,
[Glitch.Isolation]: 3,
[Glitch.Virtualization]: 3,
[Glitch.Jamming]: 3,
};
export const giltchMultCoefficients: Record<Glitch, number> = {
[Glitch.Segmentation]: 1,
[Glitch.Roaming]: 1,
[Glitch.Encryption]: 0.1,
[Glitch.Magnetism]: 0.2,
[Glitch.Rust]: 1,
[Glitch.Friction]: 0.2,
[Glitch.Isolation]: 0.2,
[Glitch.Virtualization]: 0.2,
[Glitch.Jamming]: 0.2,
};
// vulns mult by glitch lvl
export const glitchMult = (glitch: Glitch, lvl: number) => 1 + lvl * giltchMultCoefficients[glitch];
// move hinderance
export const frictionMult = (lvl: number) => Math.pow(2.5, lvl);
// transfer slow down
export const isolationMult = (lvl: number) => Math.pow(8, lvl);
// install/uninstall slow down
export const virtualizationMult = (lvl: number) => Math.pow(5, lvl);
// reduce slow down
export const jammingMult = (lvl: number) => Math.pow(2.5, lvl);
// energy loss
export const magnetismLoss = (lvl: number) => lvl;
// How often isocket/osocke move
export const roamingTime = (lvl: number) => 30000 * Math.pow(0.7, lvl);

View File

@@ -1,83 +0,0 @@
import { Recipe } from "@nsdefs";
import { Component } from "@enums";
const make = (input: Component[], output: Component): Recipe => ({ input, output });
export const Tier1Recipes: Recipe[] = [
make([Component.R0, Component.R0], Component.R1),
make([Component.G0, Component.G0], Component.G1),
make([Component.B0, Component.B0], Component.B1),
make([Component.R0, Component.G0], Component.Y1),
make([Component.G0, Component.B0], Component.C1),
make([Component.B0, Component.R0], Component.M1),
];
export const Tier2Recipes: Recipe[] = [
make([Component.R1, Component.R1], Component.R2),
make([Component.G1, Component.G1], Component.G2),
make([Component.B1, Component.B1], Component.B2),
make([Component.R1, Component.G1], Component.Y2),
make([Component.G1, Component.B1], Component.C2),
make([Component.B1, Component.R1], Component.M2),
make([Component.Y1, Component.C1, Component.M1], Component.W2),
];
export const Tier3Recipes: Recipe[] = [
make([Component.R2, Component.R2], Component.R3),
make([Component.G2, Component.G2], Component.G3),
make([Component.B2, Component.B2], Component.B3),
make([Component.R2, Component.G2], Component.Y3),
make([Component.G2, Component.B2], Component.C3),
make([Component.B2, Component.R2], Component.M3),
make([Component.Y2, Component.C2, Component.M2], Component.W3),
];
export const Tier4Recipes: Recipe[] = [
make([Component.R3, Component.R3], Component.R4),
make([Component.G3, Component.G3], Component.G4),
make([Component.B3, Component.B3], Component.B4),
make([Component.R3, Component.G3], Component.Y4),
make([Component.G3, Component.B3], Component.C4),
make([Component.B3, Component.R3], Component.M4),
make([Component.Y3, Component.C3, Component.M3], Component.W4),
];
export const Tier5Recipes: Recipe[] = [
make([Component.R4, Component.R4], Component.R5),
make([Component.G4, Component.G4], Component.G5),
make([Component.B4, Component.B4], Component.B5),
make([Component.R4, Component.G4], Component.Y5),
make([Component.G4, Component.B4], Component.C5),
make([Component.B4, Component.R4], Component.M5),
make([Component.Y4, Component.C4, Component.M4], Component.W5),
];
export const Tier6Recipes: Recipe[] = [
make([Component.R5, Component.G5], Component.Y6),
make([Component.G5, Component.B5], Component.C6),
make([Component.B5, Component.R5], Component.M6),
make([Component.Y5, Component.C5, Component.M5], Component.W6),
];
export const Tier7Recipes: Recipe[] = [make([Component.Y6, Component.C6, Component.M6], Component.W7)];
export const recipes: Recipe[][] = [
[],
Tier1Recipes,
Tier2Recipes,
Tier3Recipes,
Tier4Recipes,
Tier5Recipes,
Tier6Recipes,
Tier7Recipes,
];

View File

@@ -1,14 +0,0 @@
// speed to move between 2 tiles
export const moveSpeed = (level: number) => 1000 / (level + 10);
// speed to reduce components
export const reduceSpeed = (level: number) => 50000 / (level + 10);
// speed to transfer components between devices
export const transferSpeed = (level: number) => 4000 / (level + 10);
// speed to install / uninstall devices and tweak ISockets
export const installSpeed = (level: number) => 100000 / (level + 10);
// time until ISocket refreshes
export const emissionSpeed = (level: number) => 100000 / (level + 10);

View File

@@ -1,12 +0,0 @@
import { myrian } from "../Myrian";
import { isDeviceBattery } from "../utils";
const applyBattery = () => {
myrian.devices.forEach((device) => {
if (!isDeviceBattery(device)) return;
const up = Math.pow(2, device.tier + 1);
device.energy = Math.min(device.energy + up, device.maxEnergy);
});
};
export const startBattery = () => setInterval(applyBattery, 1000);

View File

@@ -1,74 +0,0 @@
import { DeviceType, Glitch } from "@enums";
import { myrian, myrianSize } from "../Myrian";
import { findDevice, inMyrianBounds } from "../Myrian";
import { roamingTime } from "../formulas/glitches";
const clamp = (min: number, v: number, max: number) => Math.min(Math.max(v, min), max);
let globalOffset = 0;
const dirDiff = (v: number): number => {
globalOffset++;
const r = Math.random();
const d = v - (myrianSize - 1) / 2;
const h = d > 0 ? -1 : 1;
const dv = Math.floor(r * 3 + h * Math.random() * Math.sin(globalOffset * 0.05) * Math.abs(d)) - 1;
return clamp(-1, dv, 1);
};
const isEmpty = (x: number, y: number) => {
if (!inMyrianBounds(x, y)) return false;
return !findDevice([x, y]);
};
const dirs = [
[0, 1],
[0, -1],
[1, 0],
[-1, 0],
];
const applyRoaming = () => {
const roaming = myrian.glitches[Glitch.Roaming];
setTimeout(applyRoaming, roamingTime(roaming));
if (roaming === 0) return;
myrian.devices.forEach((device) => {
if (device.type !== DeviceType.OSocket && device.type !== DeviceType.ISocket) return;
if (device.isBusy) return;
let canMove = false;
for (const dir of dirs) {
const [dx, dy] = dir;
if (isEmpty(device.x + dx, device.y + dy)) {
canMove = true;
break;
}
}
let x = -1;
let y = -1;
if (canMove) {
let dx = dirDiff(device.x);
let dy = dirDiff(device.y);
if (dx !== 0 && dy !== 0) {
if (Math.random() > 0.5) {
dx = 0;
} else {
dy = 0;
}
}
x = device.x + dx;
y = device.y + dy;
} else {
x = Math.floor(Math.random() * myrianSize);
y = Math.floor(Math.random() * myrianSize);
}
if (findDevice([x, y])) return;
if (!inMyrianBounds(x, y)) return;
device.x = x;
device.y = y;
});
};
export const startRoaming = () => setTimeout(applyRoaming, 0);

View File

@@ -1,30 +0,0 @@
import { Bus } from "@nsdefs";
import { Glitch } from "@enums";
import { myrian, myrianSize } from "../Myrian";
import { pickOne } from "../utils";
const applyRust = () => {
myrian.rust = {};
const rust = myrian.glitches[Glitch.Rust];
for (let i = 0; i < rust * 3; i++) {
const x = Math.floor(Math.random() * myrianSize);
const y = Math.floor(Math.random() * myrianSize);
myrian.rust[`${x}:${y}`] = true;
}
};
// DO NOT use `Object.keys` on a Rustable because it will return way more than just the rustable stats.
const rustStats: (keyof Rustable)[] = ["moveLvl", "transferLvl", "reduceLvl", "installLvl", "maxEnergy"];
type Rustable = Pick<Bus, "moveLvl" | "transferLvl" | "reduceLvl" | "installLvl" | "maxEnergy">;
export const rustBus = (bus: Bus, rust: number) => {
const rustable = bus as Rustable;
const nonZero = rustStats.filter((stat) => rustable[stat] > 0);
const chosen = pickOne(nonZero);
rustable[chosen] = Math.max(0, rustable[chosen] - rust * 0.1);
// cap energy when maxEnergy is reduced
bus.energy = Math.min(bus.energy, bus.maxEnergy);
};
export const startRust = () => setInterval(applyRust, 30000);

View File

@@ -1,16 +0,0 @@
import { Glitch } from "@enums";
import { myrian, myrianSize } from "../Myrian";
import { findDevice } from "../Myrian";
import { NewLock } from "../NewDevices";
const applySegmentation = () => {
const segmentation = myrian.glitches[Glitch.Segmentation];
for (let i = 0; i < segmentation; i++) {
const x = Math.floor(Math.random() * myrianSize);
const y = Math.floor(Math.random() * myrianSize);
if (findDevice([x, y])) continue;
NewLock(`lock-${x}-${y}`, x, y);
}
};
export const startSegmentation = () => setInterval(applySegmentation, 30000);

View File

@@ -1 +0,0 @@
New device that makes special requests like "has red" | "at least tier 3" | "does not contain blue" that increases the reward.

View File

@@ -1,22 +0,0 @@
import React from "react";
import { Battery } from "@nsdefs";
import BatteryChargingFullIcon from "@mui/icons-material/BatteryChargingFull";
import { defaultIconStyle } from "./common";
import { styled } from "@mui/styles";
import { DeviceTooltip } from "./DeviceTooltip";
import { TooltipTier } from "./TooltipTier";
import { TooltipEnergy } from "./TooltipEnergy";
const Template = styled(BatteryChargingFullIcon)(defaultIconStyle);
const Icon = <Template />;
interface IBatteryIconProps {
battery: Battery;
}
export const BatteryIcon = ({ battery }: IBatteryIconProps): React.ReactElement => (
<DeviceTooltip device={battery} icon={Icon}>
<TooltipTier device={battery} />
<TooltipEnergy device={battery} />
</DeviceTooltip>
);

View File

@@ -1,27 +0,0 @@
import React from "react";
import { defaultIconStyle } from "./common";
import DirectionsBusIcon from "@mui/icons-material/DirectionsBus";
import { styled } from "@mui/styles";
import { Bus } from "@nsdefs";
import { TooltipContent } from "./TooltipContent";
import { DeviceTooltip } from "./DeviceTooltip";
import { TooltipEnergy } from "./TooltipEnergy";
import { Typography } from "@mui/material";
const Template = styled(DirectionsBusIcon)(defaultIconStyle);
const Icon = <Template />;
interface IBusIconProps {
bus: Bus;
}
export const BusIcon = ({ bus }: IBusIconProps): React.ReactElement => (
<DeviceTooltip device={bus} icon={Icon}>
<Typography>moveLvl: {bus.moveLvl}</Typography>
<Typography>transferLvl: {bus.transferLvl}</Typography>
<Typography>reduceLvl: {bus.reduceLvl}</Typography>
<Typography>installLvl: {bus.installLvl}</Typography>
<TooltipEnergy device={bus} />
<TooltipContent device={bus} />
</DeviceTooltip>
);

View File

@@ -1,20 +0,0 @@
import React from "react";
import { TooltipContent } from "./TooltipContent";
import CheckBoxOutlineBlankIcon from "@mui/icons-material/CheckBoxOutlineBlank";
import { styled } from "@mui/styles";
import { defaultIconStyle } from "./common";
import { Cache } from "@nsdefs";
import { DeviceTooltip } from "./DeviceTooltip";
const Template = styled(CheckBoxOutlineBlankIcon)(defaultIconStyle);
const Icon = <Template />;
interface ICacheIconProps {
cache: Cache;
}
export const CacheIcon = ({ cache }: ICacheIconProps): React.ReactElement => (
<DeviceTooltip device={cache} icon={Icon}>
<TooltipContent device={cache} />
</DeviceTooltip>
);

View File

@@ -1,14 +0,0 @@
import React from "react";
import { Component } from "@enums";
import { getComponentColor } from "./common";
interface IComponent {
component: Component;
}
export const ComponentText = ({ component }: IComponent): React.ReactElement => (
<span style={{ color: getComponentColor(component) }}>
{component}
<br />
</span>
);

View File

@@ -1,35 +0,0 @@
import React from "react";
import { Device } from "@nsdefs";
import { DeviceType } from "@enums";
import { BusIcon } from "./BusIcon";
import { ReducerIcon } from "./Reducer";
import { LockIcon } from "./LockIcon";
import { CacheIcon } from "./CacheIcon";
import { BatteryIcon } from "./BatteryIcon";
import { OSocketIcon } from "./OSocketIcon";
import { ISocketIcon } from "./ISocketIcon";
import {
isDeviceBattery,
isDeviceBus,
isDeviceCache,
isDeviceISocket,
isDeviceLock,
isDeviceOSocket,
isDeviceReducer,
} from "../utils";
interface IDeviceIconProps {
device: Device;
}
export const DeviceIcon = ({ device }: IDeviceIconProps): React.ReactElement => {
if (isDeviceISocket(device)) return <ISocketIcon socket={device} />;
if (isDeviceOSocket(device)) return <OSocketIcon socket={device} />;
if (isDeviceBus(device)) return <BusIcon bus={device} />;
if (isDeviceReducer(device)) return <ReducerIcon reducer={device} />;
if (isDeviceLock(device)) return <LockIcon lock={device} />;
if (isDeviceCache(device)) return <CacheIcon cache={device} />;
if (isDeviceBattery(device)) return <BatteryIcon battery={device} />;
return <></>;
};

View File

@@ -1,24 +0,0 @@
import React, { ReactNode } from "react";
import { Device } from "../../ScriptEditor/NetscriptDefinitions";
import { Tooltip, Typography } from "@mui/material";
interface INewTooltipProps {
icon: React.JSX.Element;
device: Device;
children?: ReactNode;
}
export const DeviceTooltip = ({ device, icon, children }: INewTooltipProps): React.ReactElement => (
<Tooltip
title={
<>
<Typography>
{device.name} ({device.type})
</Typography>
{children}
</>
}
>
{icon}
</Tooltip>
);

View File

@@ -1,88 +0,0 @@
import React from "react";
import { cellSize } from "./common";
import { styled } from "@mui/system";
import { findDevice, myrianSize } from "../Myrian";
import { DeviceIcon } from "./DeviceIcon";
import { Typography } from "@mui/material";
const BaseCell = styled("div")({
width: cellSize,
height: cellSize,
backgroundColor: "#444",
padding: 0,
margin: "2px",
marginTop: "4px",
marginBottom: "4px",
});
const TextCell = styled(BaseCell)({
display: "flex",
alignItems: "center",
justifyContent: "center",
backgroundColor: "#00000000",
});
const DeviceCell = ({ x, y }: { x: number; y: number }): React.ReactElement => {
const device = findDevice([x, y]);
return <BaseCell>{device && <DeviceIcon device={device} />}</BaseCell>;
};
const ColD = styled("div")({
padding: 0,
margin: 0,
});
interface IColProps {
x: number;
}
const DeviceCol = ({ x }: IColProps): React.ReactElement => {
return (
<ColD>
<TextCell>
<Typography>{x}</Typography>
</TextCell>
{new Array(myrianSize).fill(undefined).map((_, y) => (
<DeviceCell key={y} x={x} y={y}></DeviceCell>
))}
</ColD>
);
};
const Table = styled("div")({
border: "1px solid #fff",
borderSpacing: "2px",
overflow: "hidden",
display: "flex",
flexDirection: "row",
paddingLeft: "2px",
paddingRight: "2px",
});
const YColumn = (
<ColD>
<TextCell>
<Typography>
&nbsp;X
<br />
Y&nbsp;
</Typography>
</TextCell>
{new Array(myrianSize).fill(undefined).map((_, y) => (
<TextCell key={y}>
<Typography>{y}</Typography>
</TextCell>
))}
</ColD>
);
export const Grid = () => (
<div style={{ display: "flex" }}>
<Table>
{YColumn}
{new Array(myrianSize).fill(undefined).map((_, x) => (
<DeviceCol key={x} x={x} />
))}
</Table>
</div>
);

View File

@@ -1,33 +0,0 @@
import { Typography } from "@mui/material";
import React from "react";
import { TooltipContent } from "./TooltipContent";
import { ISocket } from "../../ScriptEditor/NetscriptDefinitions";
import { defaultIconStyle, getComponentColor } from "./common";
import OutboxIcon from "@mui/icons-material/Outbox";
import { styled } from "@mui/styles";
import { DeviceTooltip } from "./DeviceTooltip";
import { ComponentText } from "./ComponentText";
interface IIconProps {
socket: ISocket;
col: string;
}
const Icon = styled(OutboxIcon)(({ socket, col }: IIconProps) => ({
...defaultIconStyle,
color: new Date().getTime() > socket.cooldownUntil ? col : "gray",
}));
interface IIsocketIconProps {
socket: ISocket;
}
export const ISocketIcon = ({ socket }: IIsocketIconProps) => (
<DeviceTooltip device={socket} icon={<Icon socket={socket} col={getComponentColor(socket.emitting)} />}>
<Typography>
dispensing:&nbsp;
<ComponentText component={socket.emitting} />
</Typography>
<TooltipContent device={socket} />
</DeviceTooltip>
);

View File

@@ -1,15 +0,0 @@
import React from "react";
import BlockIcon from "@mui/icons-material/Block";
import { styled } from "@mui/styles";
import { defaultIconStyle } from "./common";
import { Lock } from "@nsdefs";
import { DeviceTooltip } from "./DeviceTooltip";
const Template = styled(BlockIcon)(defaultIconStyle);
const Icon = <Template />;
interface ILockIconProps {
lock: Lock;
}
export const LockIcon = ({ lock }: ILockIconProps): React.ReactElement => <DeviceTooltip device={lock} icon={Icon} />;

View File

@@ -1,32 +0,0 @@
import React from "react";
import { Container, IconButton, Typography } from "@mui/material";
import { myrian } from "../Myrian";
import { useRerender } from "../../ui/React/hooks";
import { Info } from "@mui/icons-material";
import { dialogBoxCreate } from "../../ui/React/DialogBox";
import { MD } from "../../ui/MD/MD";
import { tutorial } from "./tutorial";
import { Grid } from "./Grid";
const tut = <MD md={tutorial} />;
export const MyrianRoot = (): React.ReactElement => {
useRerender(50);
const onHelp = () => dialogBoxCreate(tut);
return (
<Container maxWidth="lg" disableGutters sx={{ mx: 0 }}>
<Typography variant="h4">
Myrian OS
<IconButton onClick={onHelp}>
<Info />
</IconButton>
</Typography>
<Typography>
{myrian.vulns} vulns : {myrian.totalVulns} total vulns
</Typography>
<Grid />
</Container>
);
};

View File

@@ -1,35 +0,0 @@
import React from "react";
import { Typography } from "@mui/material";
import { OSocket } from "@nsdefs";
import { defaultIconStyle, getComponentColor } from "./common";
import MoveToInboxIcon from "@mui/icons-material/MoveToInbox";
import { styled } from "@mui/styles";
import { DeviceTooltip } from "./DeviceTooltip";
import { TooltipContent } from "./TooltipContent";
import { ComponentText } from "./ComponentText";
interface IIconProps {
col: string;
}
const Icon = styled(MoveToInboxIcon)(({ col }: IIconProps) => ({
...defaultIconStyle,
color: col,
}));
interface IOSocketIconProps {
socket: OSocket;
}
export const OSocketIcon = ({ socket }: IOSocketIconProps): React.ReactElement => (
<DeviceTooltip device={socket} icon={<Icon col={getComponentColor(socket.currentRequest[0])} />}>
<Typography>
requesting:
<br />
{socket.currentRequest.map((c, i) => (
<ComponentText key={i} component={c} />
))}
</Typography>
<TooltipContent device={socket} />
</DeviceTooltip>
);

View File

@@ -1,22 +0,0 @@
import React from "react";
import { TooltipContent } from "./TooltipContent";
import { Reducer } from "@nsdefs";
import MergeTypeIcon from "@mui/icons-material/MergeType";
import { styled } from "@mui/styles";
import { defaultIconStyle } from "./common";
import { DeviceTooltip } from "./DeviceTooltip";
import { TooltipTier } from "./TooltipTier";
const Template = styled(MergeTypeIcon)(defaultIconStyle);
const Icon = <Template />;
interface IReducerIconProps {
reducer: Reducer;
}
export const ReducerIcon = ({ reducer }: IReducerIconProps): React.ReactElement => (
<DeviceTooltip device={reducer} icon={Icon}>
<TooltipTier device={reducer} />
<TooltipContent device={reducer} />
</DeviceTooltip>
);

View File

@@ -1,18 +0,0 @@
import React from "react";
import { ContainerDevice } from "@nsdefs";
import { Typography } from "@mui/material";
import { ComponentText } from "./ComponentText";
export const TooltipContent = ({ device }: { device: ContainerDevice }): React.ReactElement => (
<>
{device.content.length !== 0 && (
<Typography>
content ({device.content.length} / {device.maxContent}):
<br />
{device.content.map((component, i) => (
<ComponentText key={i} component={component} />
))}
</Typography>
)}
</>
);

View File

@@ -1,13 +0,0 @@
import React from "react";
import { EnergyDevice } from "@nsdefs";
import { Typography } from "@mui/material";
interface ITooltipEnergyProps {
device: EnergyDevice;
}
export const TooltipEnergy = ({ device }: ITooltipEnergyProps): React.ReactElement => (
<Typography>
{device.energy} / {device.maxEnergy} energy
</Typography>
);

View File

@@ -1,11 +0,0 @@
import React from "react";
import { TieredDevice } from "@nsdefs";
import { Typography } from "@mui/material";
interface ITooltipTierProps {
device: TieredDevice;
}
export const TooltipTier = ({ device }: ITooltipTierProps): React.ReactElement => (
<Typography>Tier: {device.tier}</Typography>
);

View File

@@ -1,30 +0,0 @@
import { Component } from "@enums";
export const cellSize = 48;
export const defaultIconStyle = {
width: cellSize + "px",
height: cellSize + "px",
color: "white",
};
const ColorR = "red";
const ColorG = "#7CFC00";
const ColorB = "#1E90FF";
const ColorY = "yellow";
const ColorC = "cyan";
const ColorM = "magenta";
const ColorW = "white";
const componentColors: Record<string, string> = {
r: ColorR,
g: ColorG,
b: ColorB,
y: ColorY,
c: ColorC,
m: ColorM,
w: ColorW,
};
export const getComponentColor = (component: Component): string =>
componentColors[component[0].toLowerCase()] ?? ColorW;

View File

@@ -1,109 +0,0 @@
export const tutorial = `# Myrian
Myrian is the name of the OS that the BitNodes run on.
By gaining access to the OS directly you can start to break it apart by generating vulnerabilities (vulns).
You do so by interracting with the various devices in the OS, represented by a grid.
## Devices
A device is a component that takes space on the Myrian, no 2 devices can be on the same tile.
Devices can only interract when directly adjacent to each other. When one or more device is
performing an action it will become busy and no other actions can be taken until the current one finishes.
Contrary to every other mechanic in the game. Async functions using myrian functions CAN run simultenaously.
### Bus
The most important device is the Bus. Here's a few of the things it can do:
- move, only 1 tile at a time and that tile must be empty. No diagonal.
- transfer content, most entity can store items called components, bus are the only device that can transfer components to and from other devices
- use other devices, e.g. Use a Reducer device to create new components.
### ISocket
These devices produce basic components that can be used for other devices, [r0, g0, b0].
They must be picked up by busses and will eventually produce another one after a certain cooldown has passed.
### OSocket
These devices request components and produce vulns in return, a bus simply needs to transfer a component into a OSocket content in order to fulfill the request
### Reducer
These devices can be used by a bus, when being used they will first check their content, if the content matches one of the recipe they will take some time to consume their content in order to produce a new, upgraded, more valuable component, e.g. r0 + r0 => r1
### Cache
These devices act as storage for components.
### Lock
These devices cannot be installed. They appear after various conditions are fulfilled in order to block certain tiles.
### Battery
These devices are only relevant when the Magnetism glitch is active. It recharges the energy of a bus.
## Installing
Bus can install new devices, when they do so a lock will appear over the tile that will eventually become the device. The cost of any device depends on the number of that type of device currently in the OS.
### Uninstalling
A bus can remove a device, there is no refund.
## Tiers
Upgrading a reducers tier allows it to reduce components of a higher tier and ONLY that higher tier.
A tier 2 reducer can only tier 2 components like r1 + r1 => r2 and loses access to r0 + r0 => r1
Upgrading a batterys tier increases the amount of energy it produces.
## Glitches
glitches are optional difficulty modifiers that make the myrian more difficult BUT increase the amount of vulns gained.
All glitches start at level 0 and must be activated when you chose. They also have a max level that differs from glitch to glitch.
### Magnetism
By default bus lose 0 energy when moving. But when this glitch is active they start losing energy, at 0 energy bus move much more slowly. Batteries must be installed and used to charge busses.
### Friction
When Friction is active busses move more slowly.
### Rust
When rust is active, hidden tiles are set on the Myrian. When a bus steps on one of these hidden tiles one of their upgrade lowers. Higher Rust level means lowers by a larger amount and more rust tiles are set.
### Isolation
When Isolation is active busses transfer and charge more slowly.
### Segmentation
When Segmentation is active random Locks will spawn on the Myrian. You have to remove these locks or the bord will be overrun with Locks and you won't be able to move.
### Virtualization
When Virtualization is active busses install and uninstall devices more slowly.
### Jamming
When Jamming is active busses use reducers more slowly.
### Roaming
When Roaming is active, isockets and osockets start to move around the map
### Encryption
Encryption is the only glitch that's always active. The level of Encryption determines the complexity of the requests made by osockets.
## Destabilization
As the number of total vulns increase the bitnode becomes unstable and it's multiplier become more favorable.
`;

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