mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2025-02-16 18:12:24 +01:00
Merge branch 'dev' into add-simple-globs-nano-vim
This commit is contained in:
2
.gitattributes
vendored
2
.gitattributes
vendored
@ -1 +1 @@
|
||||
* text=auto
|
||||
* text=auto eol=lf
|
||||
|
8
.github/ISSUE_TEMPLATE
vendored
Normal file
8
.github/ISSUE_TEMPLATE
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
# DELETE THIS AFTER READING
|
||||
|
||||
# include (where relevant)
|
||||
|
||||
- [ ] Save file
|
||||
- [ ] Minimal scripts to reproduce the issue
|
||||
- [ ] Steps to reproduce
|
||||
- [ ] Version of the game, e.g. Bitburner v1.3.0 (216bf616)
|
11
.github/PULL_REQUEST_TEMPLATE
vendored
Normal file
11
.github/PULL_REQUEST_TEMPLATE
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
# DELETE THIS AFTER READING
|
||||
|
||||
# Documentation
|
||||
|
||||
- DO NOT CHANGE any markdown/\*.md, these files are autogenerated from NetscriptDefinitions.d.ts and will be overwritten
|
||||
- DO NOT re-generate the documentation, makes it harder to review.
|
||||
|
||||
# Bug fix
|
||||
|
||||
- Include how it was tested
|
||||
- Include screenshot / gif (if possible)
|
@ -2,3 +2,14 @@ node_modules
|
||||
package.json
|
||||
dist
|
||||
doc/build/
|
||||
doc/source
|
||||
.build
|
||||
.package
|
||||
|
||||
editor.main.js
|
||||
main.bundle.js
|
||||
index.html
|
||||
markdown
|
||||
|
||||
package.json
|
||||
package.lock.json
|
||||
|
@ -1,5 +1,6 @@
|
||||
{
|
||||
"trailingComma": "all",
|
||||
"endOfLine": "lf",
|
||||
"tabWidth": 2,
|
||||
"printWidth": 120
|
||||
}
|
||||
|
6
dist/bitburner.d.ts
vendored
6
dist/bitburner.d.ts
vendored
@ -3848,7 +3848,7 @@ export declare interface NS extends Singularity {
|
||||
* @param host - Host of target server.
|
||||
* @returns Returns the amount of time in milliseconds it takes to execute the weaken Netscript function. Returns Infinity if called on a Hacknet Server.
|
||||
*/
|
||||
getWeakenTime(host: string): number;
|
||||
getWeakenTime(host?: string): number;
|
||||
|
||||
/**
|
||||
* Get the income of a script.
|
||||
@ -3916,7 +3916,7 @@ export declare interface NS extends Singularity {
|
||||
* @param args - Formating arguments.
|
||||
* @returns Formated text.
|
||||
*/
|
||||
sprintf(format: string, ...args: string[]): string;
|
||||
sprintf(format: string, ...args: any[]): string;
|
||||
|
||||
/**
|
||||
* Format a string with an array of arguments.
|
||||
@ -3928,7 +3928,7 @@ export declare interface NS extends Singularity {
|
||||
* @param args - Formating arguments.
|
||||
* @returns Formated text.
|
||||
*/
|
||||
vsprintf(format: string, args: string[]): string;
|
||||
vsprintf(format: string, args: any[]): string;
|
||||
|
||||
/**
|
||||
* Format a number
|
||||
|
34
dist/vendor.bundle.js
vendored
34
dist/vendor.bundle.js
vendored
File diff suppressed because one or more lines are too long
@ -39,8 +39,7 @@ There are two methods of obtaining Duplicate Sleeves:
|
||||
|
||||
1. Destroy BitNode-10. Each completion give you one additional Duplicate Sleeve
|
||||
2. Purchase Duplicate Sleeves from :ref:`the faction The Covenant <gameplay_factions>`.
|
||||
This is only available in BitNodes-10 and above, and is only available after defeating
|
||||
BitNode-10 at least once. Sleeves purchased this way are **permanent** (they persist
|
||||
This is only available in BitNodes-10. Sleeves purchased this way are **permanent** (they persist
|
||||
through BitNodes). You can purchase up to 5 Duplicate Sleeves from The Covenant.
|
||||
|
||||
Synchronization
|
||||
|
@ -106,7 +106,7 @@ Check how much RAM a script requires to run with n threads
|
||||
**nano [script]**
|
||||
|
||||
Create/Edit a script. The name of the script must end with a valid
|
||||
extension: .script, .js, or .ns
|
||||
extension: .script, or .js
|
||||
|
||||
**ps**
|
||||
|
||||
|
@ -425,7 +425,7 @@ nano
|
||||
|
||||
$ nano [filename]
|
||||
|
||||
Opens up the specified file in the Text Editor. Only scripts (.script, .ns, .js) and
|
||||
Opens up the specified file in the Text Editor. Only scripts (.script, .js) and
|
||||
text files (.txt) can be edited. If the file does not already exist, then a new
|
||||
empty file will be created.
|
||||
|
||||
@ -595,7 +595,7 @@ wget
|
||||
$ wget [url] [target file]
|
||||
|
||||
Retrieves data from a url and downloads it to a file on the current server.
|
||||
The data can only be downloaded to a script (.script, .ns, .js) or a text file
|
||||
The data can only be downloaded to a script (.script, .js) or a text file
|
||||
(.txt). If the target file already exists, it will be overwritten by this command.
|
||||
|
||||
Note that will not be possible to download data from many websites because they
|
||||
|
@ -253,7 +253,6 @@ Here's what mine showed at the time I made this::
|
||||
|
||||
Take note of the following servers:
|
||||
|
||||
* |n00dles|
|
||||
* |sigma-cosmetics|
|
||||
* |joesguns|
|
||||
* |nectar-net|
|
||||
|
@ -7,7 +7,7 @@ autocomplete() Netscript Function
|
||||
|
||||
:RAM cost: 0 GB
|
||||
:param Object data: general data about the game you might want to autocomplete.
|
||||
:param string[] args: current arguments. Minus `run script.ns`
|
||||
:param string[] args: current arguments. Minus `run script.js`
|
||||
|
||||
data is an object with the following properties::
|
||||
|
||||
@ -34,6 +34,6 @@ autocomplete() Netscript Function
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ run demo.ns mega\t
|
||||
$ run demo.js mega\t
|
||||
// results in
|
||||
$ run demo.ns megacorp
|
||||
$ run demo.js megacorp
|
||||
|
@ -21,11 +21,11 @@ As of the time of writing this, a few browsers do not support `dynamic import <h
|
||||
How to use ns2
|
||||
----------------------
|
||||
Working with ns2 scripts is the same as ns1 scripts. The only difference
|
||||
is that ns2 scripts use the ".ns" or ".js" extension rather than ".script". E.g.::
|
||||
is that ns2 scripts use the ".js" extension rather than ".script". E.g.::
|
||||
|
||||
$ nano foo.ns
|
||||
$ run foo.ns -t 100 arg1 arg2 arg3
|
||||
exec("foo.ns", "purchasedServer1", "100", "randomArg");
|
||||
$ nano foo.js
|
||||
$ run foo.js -t 100 arg1 arg2 arg3
|
||||
exec("foo.js", "purchasedServer1", "100", "randomArg");
|
||||
|
||||
The caveat when using ns2 to write scripts is that your code must be
|
||||
asynchronous. Furthermore, instead of using the global scope and executing your code
|
||||
@ -66,9 +66,9 @@ Here is a summary of all rules you need to follow when writing Netscript JS code
|
||||
* **Do not write any infinite loops without using a** :code:`sleep` **or one of the timed Netscript functions like** :code:`hack`. Doing so will freeze your game.
|
||||
|
||||
* Any global variable declared in a ns2 script is shared between all instances of that
|
||||
script. For example, assume you write a script *foo.ns* and declared a global variable like so::
|
||||
script. For example, assume you write a script *foo.js* and declared a global variable like so::
|
||||
|
||||
//foo.ns
|
||||
//foo.js
|
||||
let globalVariable;
|
||||
|
||||
export async function main(ns) {
|
||||
@ -79,15 +79,15 @@ Here is a summary of all rules you need to follow when writing Netscript JS code
|
||||
}
|
||||
}
|
||||
|
||||
Then, you ran multiple instances of *foo.ns*::
|
||||
Then, you ran multiple instances of *foo.js*::
|
||||
|
||||
$ run foo.ns 1
|
||||
$ run foo.ns 1 2 3
|
||||
$ run foo.ns 1 2 3 4 5
|
||||
$ run foo.js 1
|
||||
$ run foo.js 1 2 3
|
||||
$ run foo.js 1 2 3 4 5
|
||||
|
||||
Then all three instances of foo.ns will share the same instance of :code:`globalVariable`.
|
||||
Then all three instances of foo.js will share the same instance of :code:`globalVariable`.
|
||||
(In this example, the value of :code:`globalVariable` will be set to 5 because the
|
||||
last instance of *foo.ns* to run has 5 arguments. This means that all three instances of
|
||||
last instance of *foo.js* to run has 5 arguments. This means that all three instances of
|
||||
the script will repeatedly print the value 5).
|
||||
|
||||
These global variables can be thought of as `C++ static class members <https://www.tutorialspoint.com/cplusplus/cpp_static_members.htm>`_,
|
||||
@ -117,7 +117,7 @@ early-hack-template.script
|
||||
}
|
||||
}
|
||||
|
||||
early-hack-template.ns
|
||||
early-hack-template.js
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
@ -151,8 +151,8 @@ You may have noticed that every new ns2 file will contains the following comment
|
||||
* @param {NS} ns
|
||||
**/
|
||||
|
||||
This comment is used to help the text editor autocomplete functions in the Netscript API. You can enabling it by pressing ctrl+space after `ns.`
|
||||
This comment is used to help the text editor autocomplete functions in the Netscript API. You can enable it by pressing ctrl+space after `ns.`
|
||||
|
||||
.. image:: autocomplete.png
|
||||
|
||||
The comment can be safely removed but it is recommended to keep it as it will help you.
|
||||
The comment can be safely removed but it is recommended to keep it as it will help you.
|
||||
|
@ -3,6 +3,9 @@ const greenworks = require("./greenworks");
|
||||
const log = require("electron-log");
|
||||
|
||||
function enableAchievementsInterval(window) {
|
||||
// If the Steam API could not be initialized on game start, we'll abort this.
|
||||
if (global.greenworksError) return;
|
||||
|
||||
// This is backward but the game fills in an array called `document.achievements` and we retrieve it from
|
||||
// here. Hey if it works it works.
|
||||
const steamAchievements = greenworks.getAchievementNames();
|
||||
|
@ -6,6 +6,9 @@ const achievements = require("./achievements");
|
||||
const menu = require("./menu");
|
||||
const api = require("./api-server");
|
||||
const cp = require("child_process");
|
||||
const path = require("path");
|
||||
const fs = require("fs");
|
||||
const { fileURLToPath } = require("url");
|
||||
|
||||
const debug = process.argv.includes("--debug");
|
||||
|
||||
@ -24,15 +27,54 @@ async function createWindow(killall) {
|
||||
window.show();
|
||||
if (debug) window.webContents.openDevTools();
|
||||
|
||||
window.webContents.on("new-window", function (e, url) {
|
||||
if (process.platform === "win32") {
|
||||
cp.spawn("explorer", [url], { detached: true, stdio: "ignore" });
|
||||
} else {
|
||||
// make sure local urls stay in electron perimeter
|
||||
if (url.substr(0, "file://".length) === "file://") {
|
||||
return;
|
||||
window.webContents.on("new-window", async function (e, url) {
|
||||
// Let's make sure sure we have a proper url
|
||||
let parsedUrl
|
||||
try {
|
||||
parsedUrl = new URL(url);
|
||||
} catch (_) {
|
||||
// This is an invalid url, let's just do nothing
|
||||
log.warn(`Invalid url found: ${url}`)
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
// make sure local urls stay in electron perimeter
|
||||
if (url.substr(0, "file://".length) === "file://") {
|
||||
const requestedPath = fileURLToPath(url);
|
||||
const appPath = path.parse(app.getAppPath());
|
||||
const filePath = path.parse(requestedPath);
|
||||
const isChild = filePath.dir.startsWith(appPath.dir);
|
||||
|
||||
// eslint-disable-next-line no-sync
|
||||
const fileExists = fs.existsSync(requestedPath);
|
||||
|
||||
if (!isChild) {
|
||||
// If we're not relative to our app's path let's abort
|
||||
log.warn(`Requested path ${filePath.dir}${path.sep}${filePath.base} is not relative to the app: ${appPath.dir}${path.sep}${appPath.base}`)
|
||||
e.preventDefault();
|
||||
} else if (!fileExists) {
|
||||
// If the file does not exist let's abort
|
||||
log.warn(`Requested path ${filePath.dir}${path.sep}${filePath.base} does not exist`)
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (process.platform === "win32") {
|
||||
// If we have parameters in the URL, explorer.exe won't register the URL and will open the file explorer instead.
|
||||
let urlToOpen = parsedUrl.toString();
|
||||
if (parsedUrl.search) {
|
||||
log.log(`Cannot open a path with parameters: ${parsedUrl.search}`);
|
||||
urlToOpen = urlToOpen.replace(parsedUrl.search, '');
|
||||
// It would be possible to launch an URL with parameter using this, but it would mess up the process again...
|
||||
// const escapedUri = parsedUrl.href.replace('&', '^&');
|
||||
// cp.spawn("cmd.exe", ["/c", "start", escapedUri], { detached: true, stdio: "ignore" });
|
||||
}
|
||||
|
||||
cp.spawn("explorer", [urlToOpen], { detached: true, stdio: "ignore" });
|
||||
} else {
|
||||
// and open every other protocols on the browser
|
||||
utils.openExternal(url);
|
||||
}
|
||||
|
@ -16,10 +16,18 @@ process.on('uncaughtException', function () {
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
if (greenworks.init()) {
|
||||
log.info("Steam API has been initialized.");
|
||||
} else {
|
||||
log.warn("Steam API has failed to initialize.");
|
||||
// We want to fail gracefully if we cannot connect to Steam
|
||||
try {
|
||||
if (greenworks.init()) {
|
||||
log.info("Steam API has been initialized.");
|
||||
} else {
|
||||
const error = "Steam API has failed to initialize.";
|
||||
log.warn(error);
|
||||
global.greenworksError = error;
|
||||
}
|
||||
} catch (ex) {
|
||||
log.warn(ex.message);
|
||||
global.greenworksError = ex.message;
|
||||
}
|
||||
|
||||
function setStopProcessHandler(app, window, enabled) {
|
||||
@ -113,4 +121,13 @@ app.whenReady().then(async () => {
|
||||
} else {
|
||||
startWindow(process.argv.includes("--no-scripts"));
|
||||
}
|
||||
|
||||
if (global.greenworksError) {
|
||||
dialog.showMessageBox({
|
||||
title: 'Bitburner',
|
||||
message: 'Could not connect to Steam',
|
||||
detail: `${global.greenworksError}\n\nYou won't be able to receive achievements until this is resolved and you restart the game.`,
|
||||
type: 'warning', buttons: ['OK']
|
||||
});
|
||||
}
|
||||
});
|
||||
|
@ -48,6 +48,11 @@
|
||||
</script>
|
||||
|
||||
<style>
|
||||
html, body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: 100%;
|
||||
}
|
||||
body {
|
||||
background-color: black;
|
||||
}
|
||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -9,15 +9,15 @@ Format a string.
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
sprintf(format: string, ...args: string[]): string;
|
||||
sprintf(format: string, ...args: any[]): string;
|
||||
```
|
||||
|
||||
## Parameters
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| format | string | String to format. |
|
||||
| args | string\[\] | Formating arguments. |
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ------- | -------------------- |
|
||||
| format | string | String to format. |
|
||||
| args | any\[\] | Formating arguments. |
|
||||
|
||||
<b>Returns:</b>
|
||||
|
||||
@ -30,4 +30,3 @@ Formated text.
|
||||
RAM cost: 0 GB
|
||||
|
||||
see: https://github.com/alexei/sprintf.js
|
||||
|
||||
|
@ -9,15 +9,15 @@ Format a string with an array of arguments.
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
vsprintf(format: string, args: string[]): string;
|
||||
vsprintf(format: string, args: any[]): string;
|
||||
```
|
||||
|
||||
## Parameters
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| format | string | String to format. |
|
||||
| args | string\[\] | Formating arguments. |
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ------- | -------------------- |
|
||||
| format | string | String to format. |
|
||||
| args | any\[\] | Formating arguments. |
|
||||
|
||||
<b>Returns:</b>
|
||||
|
||||
@ -30,4 +30,3 @@ Formated text.
|
||||
RAM cost: 0 GB
|
||||
|
||||
see: https://github.com/alexei/sprintf.js
|
||||
|
||||
|
23
markdown/bitburner.userinterface.getstyles.md
Normal file
23
markdown/bitburner.userinterface.getstyles.md
Normal file
@ -0,0 +1,23 @@
|
||||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [bitburner](./bitburner.md) > [UserInterface](./bitburner.userinterface.md) > [getStyles](./bitburner.userinterface.getstyles.md)
|
||||
|
||||
## UserInterface.getStyles() method
|
||||
|
||||
Get the current styles
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
getStyles(): IStyleSettings;
|
||||
```
|
||||
<b>Returns:</b>
|
||||
|
||||
IStyleSettings
|
||||
|
||||
An object containing the player's styles
|
||||
|
||||
## Remarks
|
||||
|
||||
RAM cost: cost: 0 GB
|
||||
|
@ -16,7 +16,10 @@ interface UserInterface
|
||||
|
||||
| Method | Description |
|
||||
| --- | --- |
|
||||
| [getStyles()](./bitburner.userinterface.getstyles.md) | Get the current styles |
|
||||
| [getTheme()](./bitburner.userinterface.gettheme.md) | Get the current theme |
|
||||
| [resetStyles()](./bitburner.userinterface.resetstyles.md) | Resets the player's styles to the default values |
|
||||
| [resetTheme()](./bitburner.userinterface.resettheme.md) | Resets the player's theme to the default values |
|
||||
| [setStyles(newStyles)](./bitburner.userinterface.setstyles.md) | Sets the current styles |
|
||||
| [setTheme(newTheme)](./bitburner.userinterface.settheme.md) | Sets the current theme |
|
||||
|
||||
|
21
markdown/bitburner.userinterface.resetstyles.md
Normal file
21
markdown/bitburner.userinterface.resetstyles.md
Normal file
@ -0,0 +1,21 @@
|
||||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [bitburner](./bitburner.md) > [UserInterface](./bitburner.userinterface.md) > [resetStyles](./bitburner.userinterface.resetstyles.md)
|
||||
|
||||
## UserInterface.resetStyles() method
|
||||
|
||||
Resets the player's styles to the default values
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
resetStyles(): void;
|
||||
```
|
||||
<b>Returns:</b>
|
||||
|
||||
void
|
||||
|
||||
## Remarks
|
||||
|
||||
RAM cost: cost: 0 GB
|
||||
|
38
markdown/bitburner.userinterface.setstyles.md
Normal file
38
markdown/bitburner.userinterface.setstyles.md
Normal file
@ -0,0 +1,38 @@
|
||||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [bitburner](./bitburner.md) > [UserInterface](./bitburner.userinterface.md) > [setStyles](./bitburner.userinterface.setstyles.md)
|
||||
|
||||
## UserInterface.setStyles() method
|
||||
|
||||
Sets the current styles
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
setStyles(newStyles: IStyleSettings): void;
|
||||
```
|
||||
|
||||
## Parameters
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| newStyles | IStyleSettings | |
|
||||
|
||||
<b>Returns:</b>
|
||||
|
||||
void
|
||||
|
||||
## Remarks
|
||||
|
||||
RAM cost: cost: 0 GB
|
||||
|
||||
## Example
|
||||
|
||||
Usage example (NS2)
|
||||
|
||||
```ts
|
||||
const styles = ns.ui.getStyles();
|
||||
styles.fontFamily = 'Comic Sans Ms';
|
||||
ns.ui.setStyles(styles);
|
||||
```
|
||||
|
@ -209,7 +209,7 @@
|
||||
},
|
||||
"STOCK_1q": {
|
||||
"ID": "STOCK_1q",
|
||||
"Name": "Wolf of wall stree.",
|
||||
"Name": "Wolf of wall street.",
|
||||
"Description": "Make 1q on the stock market."
|
||||
},
|
||||
"DISCOUNT": {
|
||||
@ -483,4 +483,5 @@
|
||||
"Description": "Open the dev menu."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -29,7 +29,7 @@ export function AchievementList({ achievements, playerAchievements }: IProps): J
|
||||
const secret = data.filter(entry => !entry.unlockedOn && entry.achievement.Secret)
|
||||
|
||||
// Locked behind locked content (bitnode x)
|
||||
const unavailable = data.filter(entry => !entry.unlockedOn && !entry.achievement.Secret && entry.achievement.Visible && entry.achievement.Visible());
|
||||
const unavailable = data.filter(entry => !entry.unlockedOn && !entry.achievement.Secret && entry.achievement.Visible && !entry.achievement.Visible());
|
||||
|
||||
// Remaining achievements
|
||||
const locked = data
|
||||
@ -53,7 +53,7 @@ export function AchievementList({ achievements, playerAchievements }: IProps): J
|
||||
</AccordionSummary>
|
||||
<AccordionDetails sx={{ pt: 2 }}>
|
||||
{unlocked.map(item => (
|
||||
<AchievementEntry key={item.achievement.ID}
|
||||
<AchievementEntry key={`unlocked_${item.achievement.ID}`}
|
||||
achievement={item.achievement}
|
||||
unlockedOn={item.unlockedOn}
|
||||
cssFiltersUnlocked={cssPrimary}
|
||||
@ -72,7 +72,7 @@ export function AchievementList({ achievements, playerAchievements }: IProps): J
|
||||
</AccordionSummary>
|
||||
<AccordionDetails sx={{ pt: 2 }}>
|
||||
{locked.map(item => (
|
||||
<AchievementEntry key={item.achievement.ID}
|
||||
<AchievementEntry key={`locked_${item.achievement.ID}`}
|
||||
achievement={item.achievement}
|
||||
cssFiltersUnlocked={cssPrimary}
|
||||
cssFiltersLocked={cssSecondary} />
|
||||
@ -106,10 +106,10 @@ export function AchievementList({ achievements, playerAchievements }: IProps): J
|
||||
<AccordionDetails>
|
||||
<Typography color="secondary" sx={{ mt: 1 }}>
|
||||
{secret.map(item => (
|
||||
<>
|
||||
<span key={`secret_${item.achievement.ID}`}>
|
||||
<CorruptableText content={item.achievement.ID}></CorruptableText>
|
||||
<br />
|
||||
</>
|
||||
</span>
|
||||
))}
|
||||
</Typography>
|
||||
</AccordionDetails>
|
||||
|
@ -34,6 +34,7 @@ export interface Achievement {
|
||||
Secret?: boolean;
|
||||
Condition: () => boolean;
|
||||
Visible?: () => boolean;
|
||||
AdditionalUnlock?: string[]; // IDs of achievements that should be awarded when awarding this one
|
||||
}
|
||||
|
||||
export interface PlayerAchievement {
|
||||
@ -337,6 +338,7 @@ export const achievements: IMap<Achievement> = {
|
||||
},
|
||||
FIRST_HACKNET_NODE: {
|
||||
...achievementData["FIRST_HACKNET_NODE"],
|
||||
Icon: "node",
|
||||
Condition: () => !hasHacknetServers(Player) && Player.hacknetNodes.length > 0,
|
||||
},
|
||||
"30_HACKNET_NODE": {
|
||||
@ -509,12 +511,14 @@ export const achievements: IMap<Achievement> = {
|
||||
Icon: "HASHNET",
|
||||
Visible: () => hasAccessToSF(Player, 9),
|
||||
Condition: () => hasHacknetServers(Player) && Player.hacknetNodes.length > 0,
|
||||
AdditionalUnlock: [achievementData.FIRST_HACKNET_NODE.ID],
|
||||
},
|
||||
ALL_HACKNET_SERVER: {
|
||||
...achievementData["ALL_HACKNET_SERVER"],
|
||||
Icon: "HASHNETALL",
|
||||
Visible: () => hasAccessToSF(Player, 9),
|
||||
Condition: () => hasHacknetServers(Player) && Player.hacknetNodes.length === HacknetServerConstants.MaxServers,
|
||||
AdditionalUnlock: [achievementData["30_HACKNET_NODE"].ID],
|
||||
},
|
||||
MAX_HACKNET_SERVER: {
|
||||
...achievementData["MAX_HACKNET_SERVER"],
|
||||
@ -536,12 +540,14 @@ export const achievements: IMap<Achievement> = {
|
||||
}
|
||||
return false;
|
||||
},
|
||||
AdditionalUnlock: [achievementData.MAX_HACKNET_NODE.ID],
|
||||
},
|
||||
HACKNET_SERVER_1B: {
|
||||
...achievementData["HACKNET_SERVER_1B"],
|
||||
Icon: "HASHNETMONEY",
|
||||
Visible: () => hasAccessToSF(Player, 9),
|
||||
Condition: () => hasHacknetServers(Player) && Player.moneySourceB.hacknet >= 1e9,
|
||||
AdditionalUnlock: [achievementData.HACKNET_NODE_10M.ID],
|
||||
},
|
||||
MAX_CACHE: {
|
||||
...achievementData["MAX_CACHE"],
|
||||
@ -758,13 +764,14 @@ export const achievements: IMap<Achievement> = {
|
||||
// { ID: "FLIGHT.EXE", Condition: () => Player.getHomeComputer().programs.includes(Programs.Flight.name) },
|
||||
|
||||
export function calculateAchievements(): void {
|
||||
const availableAchievements = Object.values(achievements)
|
||||
.filter((a) => a.Condition())
|
||||
.map((a) => a.ID);
|
||||
const playerAchievements = Player.achievements.map((a) => a.ID);
|
||||
const newAchievements = availableAchievements.filter((a) => !playerAchievements.includes(a));
|
||||
|
||||
for (const id of newAchievements) {
|
||||
const missingAchievements = Object.values(achievements)
|
||||
.filter((a) => !playerAchievements.includes(a.ID) && a.Condition())
|
||||
// callback returns array of achievement id and id of any in the additional list, flatmap means we have only a 1D array
|
||||
.flatMap((a) => [a.ID, ...(a.AdditionalUnlock || [])]);
|
||||
|
||||
for (const id of missingAchievements) {
|
||||
Player.giveAchievement(id);
|
||||
}
|
||||
|
||||
|
@ -15,24 +15,28 @@ const useStyles = makeStyles(() =>
|
||||
createStyles({
|
||||
level0: {
|
||||
color: "red",
|
||||
cursor: "pointer",
|
||||
"&:hover": {
|
||||
color: "#fff",
|
||||
},
|
||||
},
|
||||
level1: {
|
||||
color: "yellow",
|
||||
cursor: "pointer",
|
||||
"&:hover": {
|
||||
color: "#fff",
|
||||
},
|
||||
},
|
||||
level2: {
|
||||
color: "#48d1cc",
|
||||
cursor: "pointer",
|
||||
"&:hover": {
|
||||
color: "#fff",
|
||||
},
|
||||
},
|
||||
level3: {
|
||||
color: "blue",
|
||||
cursor: "pointer",
|
||||
"&:hover": {
|
||||
color: "#fff",
|
||||
},
|
||||
|
@ -12,7 +12,7 @@ export const ConsoleHelpText: {
|
||||
} = {
|
||||
helpList: [
|
||||
"Use 'help [command]' to get more information about a particular Bladeburner console command.",
|
||||
"",
|
||||
" ",
|
||||
" automate [var] [val] [hi/low] Configure simple automation for Bladeburner tasks",
|
||||
" clear/cls Clear the console",
|
||||
" help [cmd] Display this help text, or help text for a specific command",
|
||||
@ -20,96 +20,103 @@ export const ConsoleHelpText: {
|
||||
" skill [action] [name] Level or display info about your Bladeburner skills",
|
||||
" start [type] [name] Start a Bladeburner action/task",
|
||||
" stop Stops your current Bladeburner action/task",
|
||||
" ",
|
||||
],
|
||||
automate: [
|
||||
"automate [var] [val] [hi/low]",
|
||||
"",
|
||||
"Usage: automate [var] [val] [hi/low]",
|
||||
" ",
|
||||
"A simple way to automate your Bladeburner actions. This console command can be used " +
|
||||
"to automatically start an action when your stamina rises above a certain threshold, and " +
|
||||
"automatically switch to another action when your stamina drops below another threshold.",
|
||||
" ",
|
||||
" automate status - Check the current status of your automation and get a brief description of what it'll do",
|
||||
" automate en - Enable the automation feature",
|
||||
" automate dis - Disable the automation feature",
|
||||
"",
|
||||
" ",
|
||||
"There are four properties that must be set for this automation to work properly. Here is how to set them:",
|
||||
"",
|
||||
" ",
|
||||
" automate stamina 100 high",
|
||||
" automate contract Tracking high",
|
||||
" automate stamina 50 low",
|
||||
" automate general 'Field Analysis' low",
|
||||
"",
|
||||
" ",
|
||||
"Using the four console commands above will set the automation to perform Tracking contracts " +
|
||||
"if your stamina is 100 or higher, and then switch to Field Analysis if your stamina drops below " +
|
||||
"50. Note that when setting the action, the name of the action is CASE-SENSITIVE. It must " +
|
||||
"exactly match whatever the name is in the UI.",
|
||||
" ",
|
||||
],
|
||||
clear: ["clear", "", "Clears the console"],
|
||||
cls: ["cls", "", "Clears the console"],
|
||||
clear: ["Usage: clear", " ", "Clears the console", " "],
|
||||
cls: ["Usage: cls", " ", "Clears the console", " "],
|
||||
help: [
|
||||
"help [command]",
|
||||
"",
|
||||
"Usage: help [command]",
|
||||
" ",
|
||||
"Running 'help' with no arguments displays the general help text, which lists all console commands " +
|
||||
"and a brief description of what they do. A command can be specified to get more specific help text " +
|
||||
"about that particular command. For example:",
|
||||
"",
|
||||
" ",
|
||||
" help automate",
|
||||
"",
|
||||
" ",
|
||||
"will display specific information about using the automate console command",
|
||||
" ",
|
||||
],
|
||||
log: [
|
||||
"log [en/dis] [type]",
|
||||
"",
|
||||
"Usage: log [en/dis] [type]",
|
||||
" ",
|
||||
"Enable or disable logging. By default, the results of completing actions such as contracts/operations are logged " +
|
||||
"in the console. There are also random events that are logged in the console as well. The five categories of " +
|
||||
"things that get logged are:",
|
||||
"",
|
||||
" ",
|
||||
"[general, contracts, ops, blackops, events]",
|
||||
"",
|
||||
" ",
|
||||
"The logging for these categories can be enabled or disabled like so:",
|
||||
"",
|
||||
" ",
|
||||
" log dis contracts - Disables logging that occurs when contracts are completed",
|
||||
" log en contracts - Enables logging that occurs when contracts are completed",
|
||||
" log dis events - Disables logging for Bladeburner random events",
|
||||
"",
|
||||
" ",
|
||||
"Logging can be universally enabled/disabled using the 'all' keyword:",
|
||||
"",
|
||||
" ",
|
||||
" log dis all",
|
||||
" log en all",
|
||||
" ",
|
||||
],
|
||||
skill: [
|
||||
"skill [action] [name]",
|
||||
"",
|
||||
"Usage: skill [action] [name]",
|
||||
" ",
|
||||
"Level or display information about your skills.",
|
||||
"",
|
||||
" ",
|
||||
"To display information about all of your skills and your multipliers, use:",
|
||||
"",
|
||||
" ",
|
||||
" skill list",
|
||||
"",
|
||||
" ",
|
||||
"To display information about a specific skill, specify the name of the skill afterwards. " +
|
||||
"Note that the name of the skill is case-sensitive. Enter it exactly as seen in the UI. If " +
|
||||
"the name of the skill has whitespace, enclose the name of the skill in double quotation marks:",
|
||||
"",
|
||||
" ",
|
||||
" skill list Reaper",
|
||||
" skill list 'Digital Observer'",
|
||||
"",
|
||||
" ",
|
||||
"This console command can also be used to level up skills:",
|
||||
"",
|
||||
" ",
|
||||
" skill level [skill name]",
|
||||
" ",
|
||||
],
|
||||
start: [
|
||||
"start [type] [name]",
|
||||
"",
|
||||
"Usage: start [type] [name]",
|
||||
" ",
|
||||
"Start an action. An action is specified by its type and its name. The " +
|
||||
"name is case-sensitive. It must appear exactly as it does in the UI. If " +
|
||||
"the name of the action has whitespace, enclose it in double quotation marks. " +
|
||||
"Valid action types include:",
|
||||
"",
|
||||
" ",
|
||||
"[general, contract, op, blackop]",
|
||||
"",
|
||||
" ",
|
||||
"Examples:",
|
||||
"",
|
||||
" ",
|
||||
" start contract Tracking",
|
||||
" start op 'Undercover Operation'",
|
||||
" ",
|
||||
],
|
||||
stop: ["stop", "", "Stop your current action and go idle."],
|
||||
stop: ["Usage: stop", " ", "Stop your current action and go idle.", " "],
|
||||
};
|
||||
|
@ -4,7 +4,6 @@ import { Console } from "./Console";
|
||||
import { AllPages } from "./AllPages";
|
||||
|
||||
import { use } from "../../ui/Context";
|
||||
import Grid from "@mui/material/Grid";
|
||||
import Box from "@mui/material/Box";
|
||||
|
||||
export function BladeburnerRoot(): React.ReactElement {
|
||||
@ -24,14 +23,10 @@ export function BladeburnerRoot(): React.ReactElement {
|
||||
if (bladeburner === null) return <></>;
|
||||
return (
|
||||
<Box display="flex" flexDirection="column">
|
||||
<Grid container>
|
||||
<Grid item xs={6}>
|
||||
<Stats bladeburner={bladeburner} player={player} router={router} />
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<Console bladeburner={bladeburner} player={player} />
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Box sx={{ display: "grid", gridTemplateColumns: "4fr 8fr", p: 1 }}>
|
||||
<Stats bladeburner={bladeburner} player={player} router={router} />
|
||||
<Console bladeburner={bladeburner} player={player} />
|
||||
</Box>
|
||||
|
||||
<AllPages bladeburner={bladeburner} player={player} />
|
||||
</Box>
|
||||
|
@ -23,7 +23,7 @@ const useStyles = makeStyles((theme: Theme) =>
|
||||
width: "100%",
|
||||
},
|
||||
input: {
|
||||
backgroundColor: "#000",
|
||||
backgroundColor: theme.colors.backgroundsecondary,
|
||||
},
|
||||
nopadding: {
|
||||
padding: theme.spacing(0),
|
||||
@ -56,6 +56,7 @@ export function Console(props: IProps): React.ReactElement {
|
||||
const classes = useStyles();
|
||||
const [command, setCommand] = useState("");
|
||||
const setRerender = useState(false)[1];
|
||||
const consoleInput = useRef<HTMLInputElement>(null);
|
||||
|
||||
function handleCommandChange(event: React.ChangeEvent<HTMLInputElement>): void {
|
||||
setCommand(event.target.value);
|
||||
@ -131,15 +132,21 @@ export function Console(props: IProps): React.ReactElement {
|
||||
}
|
||||
}
|
||||
|
||||
function handleClick(): void {
|
||||
if (!consoleInput.current) return;
|
||||
consoleInput.current.focus();
|
||||
}
|
||||
|
||||
return (
|
||||
<Paper>
|
||||
<Paper sx={{ p: 1 }}>
|
||||
<Box sx={{
|
||||
height: '60vh',
|
||||
paddingBottom: '8px',
|
||||
display: 'flex',
|
||||
alignItems: 'stretch',
|
||||
whiteSpace: 'pre-wrap',
|
||||
}}>
|
||||
}}
|
||||
onClick={handleClick}>
|
||||
<Box>
|
||||
<Logs entries={[...props.bladeburner.consoleLogs]} />
|
||||
</Box>
|
||||
@ -149,6 +156,7 @@ export function Console(props: IProps): React.ReactElement {
|
||||
autoFocus
|
||||
tabIndex={1}
|
||||
type="text"
|
||||
inputRef={consoleInput}
|
||||
value={command}
|
||||
onChange={handleCommandChange}
|
||||
onKeyDown={handleKeyDown}
|
||||
@ -171,7 +179,7 @@ interface ILogProps {
|
||||
entries: string[];
|
||||
}
|
||||
|
||||
function Logs({entries}: ILogProps): React.ReactElement {
|
||||
function Logs({ entries }: ILogProps): React.ReactElement {
|
||||
const scrollHook = useRef<HTMLUListElement>(null);
|
||||
|
||||
// TODO: Text gets shifted up as new entries appear, if the user scrolled up it should attempt to keep the text focused
|
||||
@ -182,7 +190,7 @@ function Logs({entries}: ILogProps): React.ReactElement {
|
||||
|
||||
useEffect(() => {
|
||||
scrollToBottom();
|
||||
}, [entries]);
|
||||
}, [entries.length]);
|
||||
|
||||
return (
|
||||
<List sx={{ height: "100%", overflow: "auto", p: 1 }} ref={scrollHook}>
|
||||
|
@ -3,7 +3,6 @@ import { formatNumber, convertTimeMsToTimeElapsedString } from "../../utils/Stri
|
||||
import { BladeburnerConstants } from "../data/Constants";
|
||||
import { IPlayer } from "../../PersonObjects/IPlayer";
|
||||
import { Money } from "../../ui/React/Money";
|
||||
import { StatsTable } from "../../ui/React/StatsTable";
|
||||
import { numeralWrapper } from "../../ui/numeralFormat";
|
||||
import { Factions } from "../../Faction/Factions";
|
||||
import { IRouter } from "../../ui/Router";
|
||||
@ -44,138 +43,142 @@ export function Stats(props: IProps): React.ReactElement {
|
||||
}
|
||||
|
||||
return (
|
||||
<Paper sx={{ p: 1 }}>
|
||||
<Box display="flex">
|
||||
<Tooltip title={<Typography>Your rank within the Bladeburner division.</Typography>}>
|
||||
<Typography>Rank: {formatNumber(props.bladeburner.rank, 2)}</Typography>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
<br />
|
||||
<Box display="flex">
|
||||
<Tooltip
|
||||
title={
|
||||
<Typography>
|
||||
Performing actions will use up your stamina.
|
||||
<br />
|
||||
<br />
|
||||
Your max stamina is determined primarily by your agility stat.
|
||||
<br />
|
||||
<br />
|
||||
Your stamina gain rate is determined by both your agility and your max stamina. Higher max stamina leads
|
||||
to a higher gain rate.
|
||||
<br />
|
||||
<br />
|
||||
Once your stamina falls below 50% of its max value, it begins to negatively affect the success rate of
|
||||
your contracts/operations. This penalty is shown in the overview panel. If the penalty is 15%, then this
|
||||
means your success rate would be multipled by 85% (100 - 15).
|
||||
<br />
|
||||
<br />
|
||||
Your max stamina and stamina gain rate can also be increased by training, or through skills and
|
||||
Augmentation upgrades.
|
||||
</Typography>
|
||||
}
|
||||
>
|
||||
<Typography>
|
||||
Stamina: {formatNumber(props.bladeburner.stamina, 3)} / {formatNumber(props.bladeburner.maxStamina, 3)}
|
||||
</Typography>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
<br />
|
||||
<Typography>
|
||||
Stamina Penalty: {formatNumber((1 - props.bladeburner.calculateStaminaPenalty()) * 100, 1)}%
|
||||
</Typography>
|
||||
<br />
|
||||
<Typography>Team Size: {formatNumber(props.bladeburner.teamSize, 0)}</Typography>
|
||||
<Typography>Team Members Lost: {formatNumber(props.bladeburner.teamLost, 0)}</Typography>
|
||||
<br />
|
||||
<Typography>Num Times Hospitalized: {props.bladeburner.numHosp}</Typography>
|
||||
<Typography>
|
||||
Money Lost From Hospitalizations: <Money money={props.bladeburner.moneyLost} />
|
||||
</Typography>
|
||||
<br />
|
||||
<Typography>Current City: {props.bladeburner.city}</Typography>
|
||||
<Box display="flex">
|
||||
<Tooltip
|
||||
title={
|
||||
<Typography>
|
||||
This is your Bladeburner division's estimate of how many Synthoids exist in your current city. An accurate
|
||||
population count increases success rate estimates.
|
||||
</Typography>
|
||||
}
|
||||
>
|
||||
<Typography>
|
||||
Est. Synthoid Population: {numeralWrapper.formatPopulation(props.bladeburner.getCurrentCity().popEst)}
|
||||
</Typography>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
<br />
|
||||
<Box display="flex">
|
||||
<Tooltip
|
||||
title={
|
||||
<Typography>
|
||||
This is your Bladeburner divison's estimate of how many Synthoid communities exist in your current city.
|
||||
</Typography>
|
||||
}
|
||||
>
|
||||
<Typography>Synthoid Communities: {formatNumber(props.bladeburner.getCurrentCity().comms, 0)}</Typography>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
<br />
|
||||
<Box display="flex">
|
||||
<Tooltip
|
||||
title={
|
||||
<Typography>
|
||||
The city's chaos level due to tensions and conflicts between humans and Synthoids. Having too high of a
|
||||
chaos level can make contracts and operations harder.
|
||||
</Typography>
|
||||
}
|
||||
>
|
||||
<Typography>City Chaos: {formatNumber(props.bladeburner.getCurrentCity().chaos)}</Typography>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
<br />
|
||||
{(props.bladeburner.storedCycles / BladeburnerConstants.CyclesPerSecond) * 1000 > 15000 && (
|
||||
<>
|
||||
<Box display="flex">
|
||||
<Tooltip
|
||||
title={
|
||||
<Typography>
|
||||
You gain bonus time while offline or when the game is inactive (e.g. when the tab is throttled by
|
||||
browser). Bonus time makes the Bladeburner mechanic progress faster, up to 5x the normal speed.
|
||||
</Typography>
|
||||
}
|
||||
>
|
||||
<Paper sx={{ p: 1, overflowY: 'auto', overflowX: 'hidden', wordBreak: 'break-all' }}>
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1, maxHeight: '60vh' }}>
|
||||
<Box sx={{ alignSelf: 'flex-start', width: '100%' }}>
|
||||
<Button onClick={() => setTravelOpen(true)} sx={{ width: '50%' }}>Travel</Button>
|
||||
<Tooltip title={!inFaction ? <Typography>Rank 25 required.</Typography> : ""}>
|
||||
<span>
|
||||
<Button disabled={!inFaction} onClick={openFaction} sx={{ width: '50%' }}>
|
||||
Faction
|
||||
</Button>
|
||||
</span>
|
||||
</Tooltip>
|
||||
<TravelModal open={travelOpen} onClose={() => setTravelOpen(false)} bladeburner={props.bladeburner} />
|
||||
</Box>
|
||||
<Box display="flex">
|
||||
<Tooltip title={<Typography>Your rank within the Bladeburner division.</Typography>}>
|
||||
<Typography>Rank: {formatNumber(props.bladeburner.rank, 2)}</Typography>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
<br />
|
||||
<Box display="flex">
|
||||
<Tooltip
|
||||
title={
|
||||
<Typography>
|
||||
Bonus time:{" "}
|
||||
{convertTimeMsToTimeElapsedString(
|
||||
(props.bladeburner.storedCycles / BladeburnerConstants.CyclesPerSecond) * 1000,
|
||||
)}
|
||||
Performing actions will use up your stamina.
|
||||
<br />
|
||||
<br />
|
||||
Your max stamina is determined primarily by your agility stat.
|
||||
<br />
|
||||
<br />
|
||||
Your stamina gain rate is determined by both your agility and your max stamina. Higher max stamina leads
|
||||
to a higher gain rate.
|
||||
<br />
|
||||
<br />
|
||||
Once your stamina falls below 50% of its max value, it begins to negatively affect the success rate of
|
||||
your contracts/operations. This penalty is shown in the overview panel. If the penalty is 15%, then this
|
||||
means your success rate would be multipled by 85% (100 - 15).
|
||||
<br />
|
||||
<br />
|
||||
Your max stamina and stamina gain rate can also be increased by training, or through skills and
|
||||
Augmentation upgrades.
|
||||
</Typography>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
}
|
||||
>
|
||||
<Typography>
|
||||
Stamina: {formatNumber(props.bladeburner.stamina, 3)} / {formatNumber(props.bladeburner.maxStamina, 3)}
|
||||
</Typography>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
<br />
|
||||
<Typography>
|
||||
Stamina Penalty: {formatNumber((1 - props.bladeburner.calculateStaminaPenalty()) * 100, 1)}%
|
||||
</Typography>
|
||||
<br />
|
||||
<Typography>Team Size: {formatNumber(props.bladeburner.teamSize, 0)}</Typography>
|
||||
<Typography>Team Members Lost: {formatNumber(props.bladeburner.teamLost, 0)}</Typography>
|
||||
<br />
|
||||
<Typography>Num Times Hospitalized: {props.bladeburner.numHosp}</Typography>
|
||||
<Typography>
|
||||
Money Lost From Hospitalizations: <Money money={props.bladeburner.moneyLost} />
|
||||
</Typography>
|
||||
<br />
|
||||
<Typography>Current City: {props.bladeburner.city}</Typography>
|
||||
<Box display="flex">
|
||||
<Tooltip
|
||||
title={
|
||||
<Typography>
|
||||
This is your Bladeburner division's estimate of how many Synthoids exist in your current city. An accurate
|
||||
population count increases success rate estimates.
|
||||
</Typography>
|
||||
}
|
||||
>
|
||||
<Typography>
|
||||
Est. Synthoid Population: {numeralWrapper.formatPopulation(props.bladeburner.getCurrentCity().popEst)}
|
||||
</Typography>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
<br />
|
||||
<Box display="flex">
|
||||
<Tooltip
|
||||
title={
|
||||
<Typography>
|
||||
This is your Bladeburner divison's estimate of how many Synthoid communities exist in your current city.
|
||||
</Typography>
|
||||
}
|
||||
>
|
||||
<Typography>Synthoid Communities: {formatNumber(props.bladeburner.getCurrentCity().comms, 0)}</Typography>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
<br />
|
||||
<Box display="flex">
|
||||
<Tooltip
|
||||
title={
|
||||
<Typography>
|
||||
The city's chaos level due to tensions and conflicts between humans and Synthoids. Having too high of a
|
||||
chaos level can make contracts and operations harder.
|
||||
</Typography>
|
||||
}
|
||||
>
|
||||
<Typography>City Chaos: {formatNumber(props.bladeburner.getCurrentCity().chaos)}</Typography>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
<br />
|
||||
{(props.bladeburner.storedCycles / BladeburnerConstants.CyclesPerSecond) * 1000 > 15000 && (
|
||||
<>
|
||||
<Box display="flex">
|
||||
<Tooltip
|
||||
title={
|
||||
<Typography>
|
||||
You gain bonus time while offline or when the game is inactive (e.g. when the tab is throttled by
|
||||
browser). Bonus time makes the Bladeburner mechanic progress faster, up to 5x the normal speed.
|
||||
</Typography>
|
||||
}
|
||||
>
|
||||
<Typography>
|
||||
Bonus time:{" "}
|
||||
{convertTimeMsToTimeElapsedString(
|
||||
(props.bladeburner.storedCycles / BladeburnerConstants.CyclesPerSecond) * 1000,
|
||||
)}
|
||||
</Typography>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
<br />
|
||||
</>
|
||||
)}
|
||||
<Typography>Skill Points: {formatNumber(props.bladeburner.skillPoints, 0)}</Typography>
|
||||
<br />
|
||||
<Typography>
|
||||
Aug. Success Chance mult: {formatNumber(props.player.bladeburner_success_chance_mult * 100, 1)}%
|
||||
<br />
|
||||
</>
|
||||
)}
|
||||
<Typography>Skill Points: {formatNumber(props.bladeburner.skillPoints, 0)}</Typography>
|
||||
<br />
|
||||
<StatsTable
|
||||
rows={[
|
||||
["Aug. Success Chance mult: ", formatNumber(props.player.bladeburner_success_chance_mult * 100, 1) + "%"],
|
||||
["Aug. Max Stamina mult: ", formatNumber(props.player.bladeburner_max_stamina_mult * 100, 1) + "%"],
|
||||
["Aug. Stamina Gain mult: ", formatNumber(props.player.bladeburner_stamina_gain_mult * 100, 1) + "%"],
|
||||
["Aug. Field Analysis mult: ", formatNumber(props.player.bladeburner_analysis_mult * 100, 1) + "%"],
|
||||
]}
|
||||
/>
|
||||
<br />
|
||||
<Button onClick={() => setTravelOpen(true)}>Travel</Button>
|
||||
<Tooltip title={!inFaction ? <Typography>Rank 25 required.</Typography> : ""}>
|
||||
<span>
|
||||
<Button disabled={!inFaction} onClick={openFaction}>
|
||||
Faction
|
||||
</Button>
|
||||
</span>
|
||||
</Tooltip>
|
||||
<TravelModal open={travelOpen} onClose={() => setTravelOpen(false)} bladeburner={props.bladeburner} />
|
||||
Aug. Max Stamina mult: {formatNumber(props.player.bladeburner_max_stamina_mult * 100, 1)}%
|
||||
<br />
|
||||
Aug. Stamina Gain mult: {formatNumber(props.player.bladeburner_stamina_gain_mult * 100, 1)}%
|
||||
<br />
|
||||
Aug. Field Analysis mult: {formatNumber(props.player.bladeburner_analysis_mult * 100, 1)}%
|
||||
</Typography>
|
||||
</Box>
|
||||
</Paper>
|
||||
);
|
||||
}
|
||||
|
@ -157,7 +157,7 @@ export const FactionInfos: IMap<FactionInfo> = {
|
||||
(
|
||||
<>
|
||||
MegaCorp does what no other dares to do. We imagine. We create. We invent. We create what others have never even
|
||||
dreamed of. Our work fills the world's needs for food, water, power, and transporation on an unprecendented
|
||||
dreamed of. Our work fills the world's needs for food, water, power, and transportation on an unprecendented
|
||||
scale, in ways that no other company can.
|
||||
<br />
|
||||
<br />
|
||||
|
@ -15,6 +15,7 @@ import { hasAugmentationPrereqs } from "../FactionHelpers";
|
||||
import { use } from "../../ui/Context";
|
||||
import { Reputation } from "../../ui/React/Reputation";
|
||||
import { Favor } from "../../ui/React/Favor";
|
||||
import { numeralWrapper } from "../../ui/numeralFormat";
|
||||
|
||||
import Box from "@mui/material/Box";
|
||||
import Button from "@mui/material/Button";
|
||||
@ -203,7 +204,7 @@ export function AugmentationsPage(props: IProps): React.ReactElement {
|
||||
</Typography>
|
||||
}
|
||||
>
|
||||
<Typography>Price multiplier: x {mult.toFixed(3)}</Typography>
|
||||
<Typography>Price multiplier: x {numeralWrapper.formatMultiplier(mult)}</Typography>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
<Button onClick={() => switchSortOrder(PurchaseAugmentationsOrderSetting.Cost)}>Sort by Cost</Button>
|
||||
|
@ -182,7 +182,7 @@ export function HacknetNodeElem(props: IProps): React.ReactElement {
|
||||
</TableCell>
|
||||
<TableCell colSpan={2}>
|
||||
<Typography>
|
||||
<Money money={node.totalMoneyGenerated} player={props.player} /> (
|
||||
<Money money={node.totalMoneyGenerated} /> (
|
||||
<MoneyRate money={node.moneyGainRatePerSecond} />)
|
||||
</Typography>
|
||||
</TableCell>
|
||||
|
@ -27,6 +27,7 @@ import { GetServer } from "../../Server/AllServers";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import Grid from "@mui/material/Grid";
|
||||
import Button from "@mui/material/Button";
|
||||
import { Box } from "@mui/material";
|
||||
|
||||
interface IProps {
|
||||
player: IPlayer;
|
||||
@ -136,7 +137,7 @@ export function HacknetRoot(props: IProps): React.ReactElement {
|
||||
|
||||
{hasHacknetServers(props.player) && <Button onClick={() => setOpen(true)}>Spend Hashes on Upgrades</Button>}
|
||||
|
||||
<Grid container>{nodes}</Grid>
|
||||
<Box sx={{ display: 'grid', width: 'fit-content', gridTemplateColumns: 'repeat(3, 1fr)' }}>{nodes}</Box>
|
||||
<HashUpgradeModal open={open} onClose={() => setOpen(false)} />
|
||||
</>
|
||||
);
|
||||
|
@ -114,7 +114,7 @@ export const Literatures: IMap<Literature> = {};
|
||||
"as working for any military/defense organization or conducting any bioengineering, computing, or robotics related research.<br><br>" +
|
||||
"Unfortunately, many believe that not all of the rogue MK-VI Synthoids from the Uprising were found and destroyed, " +
|
||||
"and that many of them are blending in as normal humans in society today. In response, many nations have created " +
|
||||
"Bladeburner divisions, special military branches that are tasked with investigating and dealing with any Synthoid threads.<br><br>" +
|
||||
"Bladeburner divisions, special military branches that are tasked with investigating and dealing with any Synthoid threats.<br><br>" +
|
||||
"To this day, tensions still exist between the remaining Synthoids and humans as a result of the Uprising.<br><br>" +
|
||||
"Nobody knows what happened to the terrorist group Ascendis Totalis.";
|
||||
Literatures[fn] = new Literature(title, fn, txt);
|
||||
@ -309,7 +309,7 @@ export const Literatures: IMap<Literature> = {};
|
||||
fn = LiteratureNames.CodedIntelligence;
|
||||
txt =
|
||||
"Tremendous progress has been made in the field of Artificial Intelligence over the past few decades. " +
|
||||
"Our autonomous vehicles and transporation systems. The electronic personal assistants that control our everyday lives. " +
|
||||
"Our autonomous vehicles and transportation systems. The electronic personal assistants that control our everyday lives. " +
|
||||
"Medical, service, and manufacturing robots. All of these are examples of how far AI has come and how much it has " +
|
||||
"improved our daily lives. However, the question still remains of whether AI will ever be advanced enough to re-create " +
|
||||
"human intelligence.<br><br>" +
|
||||
|
@ -10,6 +10,7 @@ import { CoinFlip } from "../../Casino/CoinFlip";
|
||||
import { Roulette } from "../../Casino/Roulette";
|
||||
import { SlotMachine } from "../../Casino/SlotMachine";
|
||||
import { IPlayer } from "../../PersonObjects/IPlayer";
|
||||
import { Box } from "@mui/material";
|
||||
|
||||
enum GameType {
|
||||
None = "none",
|
||||
@ -33,15 +34,12 @@ export function CasinoLocation(props: IProps): React.ReactElement {
|
||||
return (
|
||||
<>
|
||||
{game === GameType.None && (
|
||||
<>
|
||||
<Box sx={{ display: 'grid', width: 'fit-content' }}>
|
||||
<Button onClick={() => updateGame(GameType.Coin)}>Play coin flip</Button>
|
||||
<br />
|
||||
<Button onClick={() => updateGame(GameType.Slots)}>Play slots</Button>
|
||||
<br />
|
||||
<Button onClick={() => updateGame(GameType.Roulette)}>Play roulette</Button>
|
||||
<br />
|
||||
<Button onClick={() => updateGame(GameType.Blackjack)}>Play blackjack</Button>
|
||||
</>
|
||||
</Box>
|
||||
)}
|
||||
{game !== GameType.None && (
|
||||
<>
|
||||
|
@ -3,7 +3,7 @@
|
||||
*
|
||||
* This subcomponent renders all of the buttons for applying to jobs at a company
|
||||
*/
|
||||
import React, { useState } from "react";
|
||||
import React, { useState, useEffect } from "react";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import Button from "@mui/material/Button";
|
||||
import Tooltip from "@mui/material/Tooltip";
|
||||
@ -36,6 +36,11 @@ export function CompanyLocation(props: IProps): React.ReactElement {
|
||||
function rerender(): void {
|
||||
setRerender((old) => !old);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const id = setInterval(rerender, 200);
|
||||
return () => clearInterval(id);
|
||||
}, []);
|
||||
/**
|
||||
* We'll keep a reference to the Company that this component is being rendered for,
|
||||
* so we don't have to look it up every time
|
||||
@ -221,108 +226,114 @@ export function CompanyLocation(props: IProps): React.ReactElement {
|
||||
</Box>
|
||||
<Typography>-------------------------</Typography>
|
||||
<br />
|
||||
<Button onClick={work}>Work</Button>
|
||||
|
||||
<Button onClick={() => setQuitOpen(true)}>Quit</Button>
|
||||
<QuitJobModal
|
||||
locName={props.locName}
|
||||
company={company}
|
||||
onQuit={rerender}
|
||||
open={quitOpen}
|
||||
onClose={() => setQuitOpen(false)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<br />
|
||||
{company.hasAgentPositions() && (
|
||||
<ApplyToJobButton
|
||||
company={company}
|
||||
entryPosType={CompanyPositions[posNames.AgentCompanyPositions[0]]}
|
||||
onClick={applyForAgentJob}
|
||||
text={"Apply for Agent Job"}
|
||||
/>
|
||||
)}
|
||||
{company.hasBusinessConsultantPositions() && (
|
||||
<ApplyToJobButton
|
||||
company={company}
|
||||
entryPosType={CompanyPositions[posNames.BusinessConsultantCompanyPositions[0]]}
|
||||
onClick={applyForBusinessConsultantJob}
|
||||
text={"Apply for Business Consultant Job"}
|
||||
/>
|
||||
)}
|
||||
{company.hasBusinessPositions() && (
|
||||
<ApplyToJobButton
|
||||
company={company}
|
||||
entryPosType={CompanyPositions[posNames.BusinessCompanyPositions[0]]}
|
||||
onClick={applyForBusinessJob}
|
||||
text={"Apply for Business Job"}
|
||||
/>
|
||||
)}
|
||||
{company.hasEmployeePositions() && (
|
||||
<ApplyToJobButton
|
||||
company={company}
|
||||
entryPosType={CompanyPositions[posNames.MiscCompanyPositions[1]]}
|
||||
onClick={applyForEmployeeJob}
|
||||
text={"Apply to be an Employee"}
|
||||
/>
|
||||
)}
|
||||
{company.hasEmployeePositions() && (
|
||||
<ApplyToJobButton
|
||||
company={company}
|
||||
entryPosType={CompanyPositions[posNames.PartTimeCompanyPositions[1]]}
|
||||
onClick={applyForPartTimeEmployeeJob}
|
||||
text={"Apply to be a part-time Employee"}
|
||||
/>
|
||||
)}
|
||||
{company.hasITPositions() && (
|
||||
<ApplyToJobButton
|
||||
company={company}
|
||||
entryPosType={CompanyPositions[posNames.ITCompanyPositions[0]]}
|
||||
onClick={applyForItJob}
|
||||
text={"Apply for IT Job"}
|
||||
/>
|
||||
)}
|
||||
{company.hasSecurityPositions() && (
|
||||
<ApplyToJobButton
|
||||
company={company}
|
||||
entryPosType={CompanyPositions[posNames.SecurityCompanyPositions[2]]}
|
||||
onClick={applyForSecurityJob}
|
||||
text={"Apply for Security Job"}
|
||||
/>
|
||||
)}
|
||||
{company.hasSoftwareConsultantPositions() && (
|
||||
<ApplyToJobButton
|
||||
company={company}
|
||||
entryPosType={CompanyPositions[posNames.SoftwareConsultantCompanyPositions[0]]}
|
||||
onClick={applyForSoftwareConsultantJob}
|
||||
text={"Apply for Software Consultant Job"}
|
||||
/>
|
||||
)}
|
||||
{company.hasSoftwarePositions() && (
|
||||
<ApplyToJobButton
|
||||
company={company}
|
||||
entryPosType={CompanyPositions[posNames.SoftwareCompanyPositions[0]]}
|
||||
onClick={applyForSoftwareJob}
|
||||
text={"Apply for Software Job"}
|
||||
/>
|
||||
)}
|
||||
{company.hasWaiterPositions() && (
|
||||
<ApplyToJobButton
|
||||
company={company}
|
||||
entryPosType={CompanyPositions[posNames.MiscCompanyPositions[0]]}
|
||||
onClick={applyForWaiterJob}
|
||||
text={"Apply to be a Waiter"}
|
||||
/>
|
||||
)}
|
||||
{company.hasWaiterPositions() && (
|
||||
<ApplyToJobButton
|
||||
company={company}
|
||||
entryPosType={CompanyPositions[posNames.PartTimeCompanyPositions[0]]}
|
||||
onClick={applyForPartTimeWaiterJob}
|
||||
text={"Apply to be a part-time Waiter"}
|
||||
/>
|
||||
)}
|
||||
{location.infiltrationData != null && <Button onClick={startInfiltration}>Infiltrate Company</Button>}
|
||||
<Box sx={{ display: 'grid', width: 'fit-content' }}>
|
||||
{isEmployedHere && (
|
||||
<Box sx={{ display: 'grid', gridTemplateColumns: '1fr 1fr' }}>
|
||||
<Button onClick={work}>Work</Button>
|
||||
<Button onClick={() => setQuitOpen(true)}>Quit</Button>
|
||||
<QuitJobModal
|
||||
locName={props.locName}
|
||||
company={company}
|
||||
onQuit={rerender}
|
||||
open={quitOpen}
|
||||
onClose={() => setQuitOpen(false)}
|
||||
/>
|
||||
</Box>
|
||||
)
|
||||
|
||||
}
|
||||
{company.hasAgentPositions() && (
|
||||
<ApplyToJobButton
|
||||
company={company}
|
||||
entryPosType={CompanyPositions[posNames.AgentCompanyPositions[0]]}
|
||||
onClick={applyForAgentJob}
|
||||
text={"Apply for Agent Job"}
|
||||
/>
|
||||
)}
|
||||
{company.hasBusinessConsultantPositions() && (
|
||||
<ApplyToJobButton
|
||||
company={company}
|
||||
entryPosType={CompanyPositions[posNames.BusinessConsultantCompanyPositions[0]]}
|
||||
onClick={applyForBusinessConsultantJob}
|
||||
text={"Apply for Business Consultant Job"}
|
||||
/>
|
||||
)}
|
||||
{company.hasBusinessPositions() && (
|
||||
<ApplyToJobButton
|
||||
company={company}
|
||||
entryPosType={CompanyPositions[posNames.BusinessCompanyPositions[0]]}
|
||||
onClick={applyForBusinessJob}
|
||||
text={"Apply for Business Job"}
|
||||
/>
|
||||
)}
|
||||
{company.hasEmployeePositions() && (
|
||||
<ApplyToJobButton
|
||||
company={company}
|
||||
entryPosType={CompanyPositions[posNames.MiscCompanyPositions[1]]}
|
||||
onClick={applyForEmployeeJob}
|
||||
text={"Apply to be an Employee"}
|
||||
/>
|
||||
)}
|
||||
{company.hasEmployeePositions() && (
|
||||
<ApplyToJobButton
|
||||
company={company}
|
||||
entryPosType={CompanyPositions[posNames.PartTimeCompanyPositions[1]]}
|
||||
onClick={applyForPartTimeEmployeeJob}
|
||||
text={"Apply to be a part-time Employee"}
|
||||
/>
|
||||
)}
|
||||
{company.hasITPositions() && (
|
||||
<ApplyToJobButton
|
||||
company={company}
|
||||
entryPosType={CompanyPositions[posNames.ITCompanyPositions[0]]}
|
||||
onClick={applyForItJob}
|
||||
text={"Apply for IT Job"}
|
||||
/>
|
||||
)}
|
||||
{company.hasSecurityPositions() && (
|
||||
<ApplyToJobButton
|
||||
company={company}
|
||||
entryPosType={CompanyPositions[posNames.SecurityCompanyPositions[2]]}
|
||||
onClick={applyForSecurityJob}
|
||||
text={"Apply for Security Job"}
|
||||
/>
|
||||
)}
|
||||
{company.hasSoftwareConsultantPositions() && (
|
||||
<ApplyToJobButton
|
||||
company={company}
|
||||
entryPosType={CompanyPositions[posNames.SoftwareConsultantCompanyPositions[0]]}
|
||||
onClick={applyForSoftwareConsultantJob}
|
||||
text={"Apply for Software Consultant Job"}
|
||||
/>
|
||||
)}
|
||||
{company.hasSoftwarePositions() && (
|
||||
<ApplyToJobButton
|
||||
company={company}
|
||||
entryPosType={CompanyPositions[posNames.SoftwareCompanyPositions[0]]}
|
||||
onClick={applyForSoftwareJob}
|
||||
text={"Apply for Software Job"}
|
||||
/>
|
||||
)}
|
||||
{company.hasWaiterPositions() && (
|
||||
<ApplyToJobButton
|
||||
company={company}
|
||||
entryPosType={CompanyPositions[posNames.MiscCompanyPositions[0]]}
|
||||
onClick={applyForWaiterJob}
|
||||
text={"Apply to be a Waiter"}
|
||||
/>
|
||||
)}
|
||||
{company.hasWaiterPositions() && (
|
||||
<ApplyToJobButton
|
||||
company={company}
|
||||
entryPosType={CompanyPositions[posNames.PartTimeCompanyPositions[0]]}
|
||||
onClick={applyForPartTimeWaiterJob}
|
||||
text={"Apply to be a part-time Waiter"}
|
||||
/>
|
||||
)}
|
||||
{location.infiltrationData != null && <Button onClick={startInfiltration}>Infiltrate Company</Button>}
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -16,6 +16,7 @@ import { Server } from "../../Server/Server";
|
||||
import { Money } from "../../ui/React/Money";
|
||||
import { IRouter } from "../../ui/Router";
|
||||
import { serverMetadata } from "../../Server/data/servers";
|
||||
import { Box } from "@mui/material";
|
||||
|
||||
type IProps = {
|
||||
loc: Location;
|
||||
@ -56,7 +57,7 @@ export function GymLocation(props: IProps): React.ReactElement {
|
||||
const cost = CONSTANTS.ClassGymBaseCost * calculateCost();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box sx={{ display: 'grid', width: 'fit-content' }}>
|
||||
<Button onClick={trainStrength}>
|
||||
Train Strength (<Money money={cost} player={props.p} /> / sec)
|
||||
</Button>
|
||||
@ -72,6 +73,6 @@ export function GymLocation(props: IProps): React.ReactElement {
|
||||
<Button onClick={trainAgility}>
|
||||
Train Agility (<Money money={cost} player={props.p} /> / sec)
|
||||
</Button>
|
||||
</>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ import { Crimes } from "../../Crime/Crimes";
|
||||
|
||||
import { numeralWrapper } from "../../ui/numeralFormat";
|
||||
import { use } from "../../ui/Context";
|
||||
import { Box } from "@mui/material";
|
||||
|
||||
export function SlumsLocation(): React.ReactElement {
|
||||
const player = use.Player();
|
||||
@ -113,73 +114,61 @@ export function SlumsLocation(): React.ReactElement {
|
||||
const heistChance = Crimes.Heist.successRate(player);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box sx={{ display: 'grid', width: 'fit-content' }}>
|
||||
<Tooltip title={<>Attempt to shoplift from a low-end retailer</>}>
|
||||
<Button onClick={shoplift}>
|
||||
Shoplift ({numeralWrapper.formatPercentage(shopliftChance)} chance of success)
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<br />
|
||||
<Tooltip title={<>Attempt to commit armed robbery on a high-end store</>}>
|
||||
<Button onClick={robStore}>
|
||||
Rob store ({numeralWrapper.formatPercentage(robStoreChance)} chance of success)
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<br />
|
||||
<Tooltip title={<>Attempt to mug a random person on the street</>}>
|
||||
<Button onClick={mug}>Mug someone ({numeralWrapper.formatPercentage(mugChance)} chance of success)</Button>
|
||||
</Tooltip>
|
||||
<br />
|
||||
<Tooltip title={<>Attempt to rob property from someone's house</>}>
|
||||
<Button onClick={larceny}>Larceny ({numeralWrapper.formatPercentage(larcenyChance)} chance of success)</Button>
|
||||
</Tooltip>
|
||||
<br />
|
||||
<Tooltip title={<>Attempt to deal drugs</>}>
|
||||
<Button onClick={dealDrugs}>
|
||||
Deal Drugs ({numeralWrapper.formatPercentage(drugsChance)} chance of success)
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<br />
|
||||
<Tooltip title={<>Attempt to forge corporate bonds</>}>
|
||||
<Button onClick={bondForgery}>
|
||||
Bond Forgery ({numeralWrapper.formatPercentage(bondChance)} chance of success)
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<br />
|
||||
<Tooltip title={<>Attempt to smuggle illegal arms into the city</>}>
|
||||
<Button onClick={traffickArms}>
|
||||
Traffick illegal Arms ({numeralWrapper.formatPercentage(armsChance)} chance of success)
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<br />
|
||||
<Tooltip title={<>Attempt to murder a random person on the street</>}>
|
||||
<Button onClick={homicide}>
|
||||
Homicide ({numeralWrapper.formatPercentage(homicideChance)} chance of success)
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<br />
|
||||
<Tooltip title={<>Attempt to commit grand theft auto</>}>
|
||||
<Button onClick={grandTheftAuto}>
|
||||
Grand theft Auto ({numeralWrapper.formatPercentage(gtaChance)} chance of success)
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<br />
|
||||
<Tooltip title={<>Attempt to kidnap and ransom a high-profile-target</>}>
|
||||
<Button onClick={kidnap}>
|
||||
Kidnap and Ransom ({numeralWrapper.formatPercentage(kidnapChance)} chance of success)
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<br />
|
||||
<Tooltip title={<>Attempt to assassinate a high-profile target</>}>
|
||||
<Button onClick={assassinate}>
|
||||
Assassinate ({numeralWrapper.formatPercentage(assassinateChance)} chance of success)
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<br />
|
||||
<Tooltip title={<>Attempt to pull off the ultimate heist</>}>
|
||||
<Button onClick={heist}>Heist ({numeralWrapper.formatPercentage(heistChance)} chance of success)</Button>
|
||||
</Tooltip>
|
||||
<br />
|
||||
</>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
@ -81,7 +81,7 @@ export function SpecialLocation(props: IProps): React.ReactElement {
|
||||
return <></>;
|
||||
}
|
||||
const text = inBladeburner ? "Enter Bladeburner Headquarters" : "Apply to Bladeburner Division";
|
||||
return <Button onClick={handleBladeburner}>{text}</Button>;
|
||||
return <><br/><Button onClick={handleBladeburner}>{text}</Button></>;
|
||||
}
|
||||
|
||||
function renderNoodleBar(): React.ReactElement {
|
||||
|
@ -18,6 +18,7 @@ import { Money } from "../../ui/React/Money";
|
||||
import { use } from "../../ui/Context";
|
||||
import { PurchaseServerModal } from "./PurchaseServerModal";
|
||||
import { numeralWrapper } from "../../ui/numeralFormat";
|
||||
import { Box } from "@mui/material";
|
||||
|
||||
interface IServerProps {
|
||||
ram: number;
|
||||
@ -70,7 +71,9 @@ export function TechVendorLocation(props: IProps): React.ReactElement {
|
||||
return (
|
||||
<>
|
||||
<br />
|
||||
{purchaseServerButtons}
|
||||
<Box sx={{ display: 'grid', width: 'fit-content' }}>
|
||||
{purchaseServerButtons}
|
||||
</Box>
|
||||
<br />
|
||||
<Typography>
|
||||
<i>"You can order bigger servers via scripts. We don't take custom orders in person."</i>
|
||||
|
@ -15,6 +15,7 @@ import { Server } from "../../Server/Server";
|
||||
|
||||
import { Money } from "../../ui/React/Money";
|
||||
import { use } from "../../ui/Context";
|
||||
import { Box } from "@mui/material";
|
||||
|
||||
type IProps = {
|
||||
loc: Location;
|
||||
@ -72,45 +73,40 @@ export function UniversityLocation(props: IProps): React.ReactElement {
|
||||
const earnCharismaExpTooltip = `Gain charisma experience!`;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box sx={{ display: 'grid', width: 'fit-content' }}>
|
||||
<Tooltip title={earnHackingExpTooltip}>
|
||||
<Button onClick={study}>Study Computer Science (free)</Button>
|
||||
</Tooltip>
|
||||
<br />
|
||||
<Tooltip title={earnHackingExpTooltip}>
|
||||
<Button onClick={dataStructures}>
|
||||
Take Data Structures course (
|
||||
<Money money={dataStructuresCost} player={player} /> / sec)
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<br />
|
||||
<Tooltip title={earnHackingExpTooltip}>
|
||||
<Button onClick={networks}>
|
||||
Take Networks course (
|
||||
<Money money={networksCost} player={player} /> / sec)
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<br />
|
||||
<Tooltip title={earnHackingExpTooltip}>
|
||||
<Button onClick={algorithms}>
|
||||
Take Algorithms course (
|
||||
<Money money={algorithmsCost} player={player} /> / sec)
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<br />
|
||||
<Tooltip title={earnCharismaExpTooltip}>
|
||||
<Button onClick={management}>
|
||||
Take Management course (
|
||||
<Money money={managementCost} player={player} /> / sec)
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<br />
|
||||
<Tooltip title={earnCharismaExpTooltip}>
|
||||
<Button onClick={leadership}>
|
||||
Take Leadership course (
|
||||
<Money money={leadershipCost} player={player} /> / sec)
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
@ -364,6 +364,9 @@ export const RamCosts: IMap<any> = {
|
||||
getTheme: 0,
|
||||
setTheme: 0,
|
||||
resetTheme: 0,
|
||||
getStyles: 0,
|
||||
setStyles: 0,
|
||||
resetStyles: 0,
|
||||
},
|
||||
|
||||
heart: {
|
||||
|
@ -33,9 +33,9 @@ export class WorkerScript {
|
||||
delay: number | null = null;
|
||||
|
||||
/**
|
||||
* Holds the Promise resolve() function for when the script is "blocked" by an async op
|
||||
* Holds the Promise reject() function while the script is "blocked" by an async op
|
||||
*/
|
||||
delayResolve?: () => void;
|
||||
delayReject?: (reason?: any) => void;
|
||||
|
||||
/**
|
||||
* Stores names of all functions that have logging disabled
|
||||
|
@ -138,8 +138,8 @@ function killNetscriptDelay(workerScript: WorkerScript): void {
|
||||
if (workerScript instanceof WorkerScript) {
|
||||
if (workerScript.delay) {
|
||||
clearTimeout(workerScript.delay);
|
||||
if (workerScript.delayResolve) {
|
||||
workerScript.delayResolve();
|
||||
if (workerScript.delayReject) {
|
||||
workerScript.delayReject(workerScript);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,12 +3,17 @@ import { GetServer } from "./Server/AllServers";
|
||||
import { WorkerScript } from "./Netscript/WorkerScript";
|
||||
|
||||
export function netscriptDelay(time: number, workerScript: WorkerScript): Promise<void> {
|
||||
return new Promise(function (resolve) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
workerScript.delay = window.setTimeout(() => {
|
||||
workerScript.delay = null;
|
||||
resolve();
|
||||
workerScript.delayReject = undefined;
|
||||
|
||||
if (workerScript.env.stopFlag)
|
||||
reject(workerScript);
|
||||
else
|
||||
resolve();
|
||||
}, time);
|
||||
workerScript.delayResolve = resolve;
|
||||
workerScript.delayReject = reject;
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -172,7 +172,7 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
|
||||
throw makeRuntimeRejectMsg(
|
||||
workerScript,
|
||||
`Invalid scriptArgs argument passed into getRunningScript() from ${callingFnName}(). ` +
|
||||
`This is probably a bug. Please report to game developer`,
|
||||
`This is probably a bug. Please report to game developer`,
|
||||
);
|
||||
}
|
||||
|
||||
@ -335,16 +335,13 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
|
||||
workerScript.log(
|
||||
"hack",
|
||||
() =>
|
||||
`Executing ${hostname} in ${convertTimeMsToTimeElapsedString(
|
||||
`Executing on '${server.hostname}' in ${convertTimeMsToTimeElapsedString(
|
||||
hackingTime * 1000,
|
||||
true,
|
||||
)} (t=${numeralWrapper.formatThreads(threads)})`,
|
||||
);
|
||||
|
||||
return netscriptDelay(hackingTime * 1000, workerScript).then(function () {
|
||||
if (workerScript.env.stopFlag) {
|
||||
return Promise.reject(workerScript);
|
||||
}
|
||||
const hackChance = calculateHackingChance(server, Player);
|
||||
const rand = Math.random();
|
||||
let expGainedOnSuccess = calculateHackingExpGain(server, Player) * threads;
|
||||
@ -352,7 +349,7 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
|
||||
if (rand < hackChance) {
|
||||
// Success!
|
||||
const percentHacked = calculatePercentMoneyHacked(server, Player);
|
||||
let maxThreadNeeded = Math.ceil((1 / percentHacked) * (server.moneyAvailable / server.moneyMax));
|
||||
let maxThreadNeeded = Math.ceil(1 / percentHacked);
|
||||
if (isNaN(maxThreadNeeded)) {
|
||||
// Server has a 'max money' of 0 (probably). We'll set this to an arbitrarily large value
|
||||
maxThreadNeeded = 1e6;
|
||||
@ -432,8 +429,10 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
|
||||
throw makeRuntimeErrorMsg(funcName, `${argName} should be a string`);
|
||||
},
|
||||
number: (funcName: string, argName: string, v: any): number => {
|
||||
if (typeof v === "number") return v;
|
||||
if (!isNaN(v) && !isNaN(parseFloat(v))) return parseFloat(v);
|
||||
if (!isNaN(v)) {
|
||||
if (typeof v === "number") return v;
|
||||
if (!isNaN(parseFloat(v))) return parseFloat(v);
|
||||
}
|
||||
throw makeRuntimeErrorMsg(funcName, `${argName} should be a number`);
|
||||
},
|
||||
boolean: (v: any): boolean => {
|
||||
@ -613,9 +612,6 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
|
||||
)} (t=${numeralWrapper.formatThreads(threads)}).`,
|
||||
);
|
||||
return netscriptDelay(growTime * 1000, workerScript).then(function () {
|
||||
if (workerScript.env.stopFlag) {
|
||||
return Promise.reject(workerScript);
|
||||
}
|
||||
const moneyBefore = server.moneyAvailable <= 0 ? 1 : server.moneyAvailable;
|
||||
processSingleServerGrowth(server, threads, Player, host.cpuCores);
|
||||
const moneyAfter = server.moneyAvailable;
|
||||
@ -684,7 +680,6 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
|
||||
)} (t=${numeralWrapper.formatThreads(threads)})`,
|
||||
);
|
||||
return netscriptDelay(weakenTime * 1000, workerScript).then(function () {
|
||||
if (workerScript.env.stopFlag) return Promise.reject(workerScript);
|
||||
const host = GetServer(workerScript.hostname);
|
||||
if (host === null) {
|
||||
workerScript.log("weaken", () => "Server is null, did it die?");
|
||||
@ -697,8 +692,7 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
|
||||
workerScript.log(
|
||||
"weaken",
|
||||
() =>
|
||||
`'${server.hostname}' security level weakened to ${
|
||||
server.hackDifficulty
|
||||
`'${server.hostname}' security level weakened to ${server.hackDifficulty
|
||||
}. Gained ${numeralWrapper.formatExp(expGain)} hacking exp (t=${numeralWrapper.formatThreads(threads)})`,
|
||||
);
|
||||
workerScript.scriptRef.onlineExpGained += expGain;
|
||||
@ -1901,7 +1895,7 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
|
||||
const iport = helper.getValidPort("clearPort", port);
|
||||
return iport.clear();
|
||||
},
|
||||
getPortHandle: function (port: any): any {
|
||||
getPortHandle: function (port: any): IPort {
|
||||
updateDynamicRam("getPortHandle", getRamCost(Player, "getPortHandle"));
|
||||
const iport = helper.getValidPort("getPortHandle", port);
|
||||
return iport;
|
||||
@ -2011,7 +2005,7 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
|
||||
|
||||
return calculateGrowTime(server, Player) * 1000;
|
||||
},
|
||||
getWeakenTime: function (hostname: any): any {
|
||||
getWeakenTime: function (hostname: any = workerScript.hostname): any {
|
||||
updateDynamicRam("getWeakenTime", getRamCost(Player, "getWeakenTime"));
|
||||
const server = safeGetServer(hostname, "getWeakenTime");
|
||||
if (!(server instanceof Server)) {
|
||||
|
@ -53,23 +53,22 @@ export function NetscriptCodingContract(
|
||||
const serv = helper.getServer(hostname, "codingcontract.attempt");
|
||||
if (contract.isSolution(answer)) {
|
||||
const reward = player.gainCodingContractReward(creward, contract.getDifficulty());
|
||||
workerScript.log("attempt", () => `Successfully completed Coding Contract '${filename}'. Reward: ${reward}`);
|
||||
workerScript.log("codingcontract.attempt", () => `Successfully completed Coding Contract '${filename}'. Reward: ${reward}`);
|
||||
serv.removeContract(filename);
|
||||
return returnReward ? reward : true;
|
||||
} else {
|
||||
++contract.tries;
|
||||
if (contract.tries >= contract.getMaxNumTries()) {
|
||||
workerScript.log(
|
||||
"attempt",
|
||||
"codingcontract.attempt",
|
||||
() => `Coding Contract attempt '${filename}' failed. Contract is now self-destructing`,
|
||||
);
|
||||
serv.removeContract(filename);
|
||||
} else {
|
||||
workerScript.log(
|
||||
"attempt",
|
||||
"codingcontract.attempt",
|
||||
() =>
|
||||
`Coding Contract attempt '${filename}' failed. ${
|
||||
contract.getMaxNumTries() - contract.tries
|
||||
`Coding Contract attempt '${filename}' failed. ${contract.getMaxNumTries() - contract.tries
|
||||
} attempts remaining.`,
|
||||
);
|
||||
}
|
||||
|
@ -311,9 +311,6 @@ export function NetscriptCorporation(
|
||||
const job = helper.string("assignJob", "job", ajob);
|
||||
const employee = getEmployee(divisionName, cityName, employeeName);
|
||||
return netscriptDelay(1000, workerScript).then(function () {
|
||||
if (workerScript.env.stopFlag) {
|
||||
return Promise.reject(workerScript);
|
||||
}
|
||||
return Promise.resolve(AssignJob(employee, job));
|
||||
});
|
||||
},
|
||||
@ -344,9 +341,6 @@ export function NetscriptCorporation(
|
||||
(60 * 1000) / (player.hacking_speed_mult * calculateIntelligenceBonus(player.intelligence, 1)),
|
||||
workerScript,
|
||||
).then(function () {
|
||||
if (workerScript.env.stopFlag) {
|
||||
return Promise.reject(workerScript);
|
||||
}
|
||||
return Promise.resolve(ThrowParty(corporation, office, costPerEmployee));
|
||||
});
|
||||
},
|
||||
@ -359,9 +353,6 @@ export function NetscriptCorporation(
|
||||
(60 * 1000) / (player.hacking_speed_mult * calculateIntelligenceBonus(player.intelligence, 1)),
|
||||
workerScript,
|
||||
).then(function () {
|
||||
if (workerScript.env.stopFlag) {
|
||||
return Promise.reject(workerScript);
|
||||
}
|
||||
return Promise.resolve(BuyCoffee(corporation, getDivision(divisionName), getOffice(divisionName, cityName)));
|
||||
});
|
||||
},
|
||||
|
@ -111,7 +111,7 @@ export function NetscriptHacknet(player: IPlayer, workerScript: WorkerScript, he
|
||||
}
|
||||
const node = getHacknetNode(i, "upgradeCache");
|
||||
if (!(node instanceof HacknetServer)) {
|
||||
workerScript.log("upgradeCache", () => "Can only be called on hacknet servers");
|
||||
workerScript.log("hacknet.upgradeCache", () => "Can only be called on hacknet servers");
|
||||
return false;
|
||||
}
|
||||
const res = purchaseCacheUpgrade(player, node, n);
|
||||
@ -138,7 +138,7 @@ export function NetscriptHacknet(player: IPlayer, workerScript: WorkerScript, he
|
||||
}
|
||||
const node = getHacknetNode(i, "upgradeCache");
|
||||
if (!(node instanceof HacknetServer)) {
|
||||
workerScript.log("getCacheUpgradeCost", () => "Can only be called on hacknet servers");
|
||||
workerScript.log("hacknet.getCacheUpgradeCost", () => "Can only be called on hacknet servers");
|
||||
return -1;
|
||||
}
|
||||
return node.calculateCacheUpgradeCost(n);
|
||||
|
@ -611,9 +611,6 @@ export function NetscriptSingularity(
|
||||
);
|
||||
|
||||
return netscriptDelay(installTime, workerScript).then(function () {
|
||||
if (workerScript.env.stopFlag) {
|
||||
return Promise.reject(workerScript);
|
||||
}
|
||||
workerScript.log("installBackdoor", () => `Successfully installed backdoor on '${server.hostname}'`);
|
||||
|
||||
server.backdoorInstalled = true;
|
||||
|
@ -38,9 +38,6 @@ export function NetscriptStanek(player: IPlayer, workerScript: WorkerScript, hel
|
||||
if (!fragment) throw helper.makeRuntimeErrorMsg("stanek.charge", `No fragment with root (${rootX}, ${rootY}).`);
|
||||
const time = staneksGift.inBonus() ? 200 : 1000;
|
||||
return netscriptDelay(time, workerScript).then(function () {
|
||||
if (workerScript.env.stopFlag) {
|
||||
return Promise.reject(workerScript);
|
||||
}
|
||||
const charge = staneksGift.charge(player, fragment, workerScript.scriptRef.threads);
|
||||
workerScript.log("stanek.charge", () => `Charged fragment for ${charge} charge.`);
|
||||
return Promise.resolve();
|
||||
|
@ -2,10 +2,11 @@ import { INetscriptHelper } from "./INetscriptHelper";
|
||||
import { WorkerScript } from "../Netscript/WorkerScript";
|
||||
import { IPlayer } from "../PersonObjects/IPlayer";
|
||||
import { getRamCost } from "../Netscript/RamCostGenerator";
|
||||
import { UserInterface as IUserInterface, UserInterfaceTheme } from "../ScriptEditor/NetscriptDefinitions";
|
||||
import { IStyleSettings, UserInterface as IUserInterface, UserInterfaceTheme } from "../ScriptEditor/NetscriptDefinitions";
|
||||
import { Settings } from "../Settings/Settings";
|
||||
import { ThemeEvents } from "../ui/React/Theme";
|
||||
import { defaultTheme } from "../Settings/Themes";
|
||||
import { defaultStyles } from "../Settings/Styles";
|
||||
|
||||
export function NetscriptUserInterface(
|
||||
player: IPlayer,
|
||||
@ -18,6 +19,11 @@ export function NetscriptUserInterface(
|
||||
return { ...Settings.theme };
|
||||
},
|
||||
|
||||
getStyles: function (): IStyleSettings {
|
||||
helper.updateDynamicRam("getStyles", getRamCost(player, "ui", "getStyles"));
|
||||
return { ...Settings.styles };
|
||||
},
|
||||
|
||||
setTheme: function (newTheme: UserInterfaceTheme): void {
|
||||
helper.updateDynamicRam("setTheme", getRamCost(player, "ui", "setTheme"));
|
||||
const hex = /^(#)((?:[A-Fa-f0-9]{3}){1,2})$/;
|
||||
@ -43,11 +49,41 @@ export function NetscriptUserInterface(
|
||||
}
|
||||
},
|
||||
|
||||
setStyles: function (newStyles: IStyleSettings): void {
|
||||
helper.updateDynamicRam("setStyles", getRamCost(player, "ui", "setStyles"));
|
||||
|
||||
const currentStyles = {...Settings.styles}
|
||||
const errors: string[] = [];
|
||||
for (const key of Object.keys(newStyles)) {
|
||||
if (!((currentStyles as any)[key])) {
|
||||
// Invalid key
|
||||
errors.push(`Invalid key "${key}"`);
|
||||
} else {
|
||||
(currentStyles as any)[key] = (newStyles as any)[key];
|
||||
}
|
||||
}
|
||||
|
||||
if (errors.length === 0) {
|
||||
Object.assign(Settings.styles, currentStyles);
|
||||
ThemeEvents.emit();
|
||||
workerScript.log("ui.setStyles", () => `Successfully set styles`);
|
||||
} else {
|
||||
workerScript.log("ui.setStyles", () => `Failed to set styles. Errors: ${errors.join(', ')}`);
|
||||
}
|
||||
},
|
||||
|
||||
resetTheme: function (): void {
|
||||
helper.updateDynamicRam("resetTheme", getRamCost(player, "ui", "resetTheme"));
|
||||
Settings.theme = defaultTheme;
|
||||
Settings.theme = { ...defaultTheme };
|
||||
ThemeEvents.emit();
|
||||
workerScript.log("ui.resetTheme", () => `Reinitialized theme to default`);
|
||||
},
|
||||
|
||||
resetStyles: function (): void {
|
||||
helper.updateDynamicRam("resetStyles", getRamCost(player, "ui", "resetStyles"));
|
||||
Settings.styles = { ...defaultStyles };
|
||||
ThemeEvents.emit();
|
||||
workerScript.log("ui.resetStyles", () => `Reinitialized styles to default`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -151,6 +151,24 @@ function _getScriptUrls(script: Script, scripts: Script[], seen: Script[]): Scri
|
||||
start: node.source.range[0] + 1,
|
||||
end: node.source.range[1] - 1
|
||||
});
|
||||
},
|
||||
ExportNamedDeclaration(node: any) {
|
||||
if (node.source) {
|
||||
importNodes.push({
|
||||
filename: node.source.value,
|
||||
start: node.source.range[0] + 1,
|
||||
end: node.source.range[1] - 1
|
||||
});
|
||||
}
|
||||
},
|
||||
ExportAllDeclaration(node: any) {
|
||||
if (node.source) {
|
||||
importNodes.push({
|
||||
filename: node.source.value,
|
||||
start: node.source.range[0] + 1,
|
||||
end: node.source.range[1] - 1
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
// Sort the nodes from last start index to first. This replaces the last import with a blob first,
|
||||
|
@ -946,11 +946,6 @@ export function workForFaction(this: IPlayer, numCycles: number): boolean {
|
||||
default:
|
||||
break;
|
||||
}
|
||||
let favorMult = 1 + faction.favor / 100;
|
||||
if (isNaN(favorMult)) {
|
||||
favorMult = 1;
|
||||
}
|
||||
this.workRepGainRate *= favorMult;
|
||||
this.workRepGainRate *= BitNodeMultipliers.FactionWorkRepGain;
|
||||
|
||||
//Cap the number of cycles being processed to whatever would put you at limit (20 hours)
|
||||
@ -1821,7 +1816,7 @@ export function getNextCompanyPosition(
|
||||
}
|
||||
|
||||
export function quitJob(this: IPlayer, company: string): void {
|
||||
if (this.isWorking == true && this.workType == "Working for Company" && this.companyName == company) {
|
||||
if (this.isWorking == true && this.workType.includes("Working for Company") && this.companyName == company) {
|
||||
this.isWorking = false;
|
||||
this.companyName = "";
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ import React, { useState, useEffect } from "react";
|
||||
import { use } from "../../ui/Context";
|
||||
import { getAvailableCreatePrograms } from "../ProgramHelpers";
|
||||
|
||||
import { Tooltip, Typography } from "@mui/material";
|
||||
import { Box, Tooltip, Typography } from "@mui/material";
|
||||
import Button from "@mui/material/Button";
|
||||
|
||||
export const ProgramsSeen: string[] = [];
|
||||
@ -38,27 +38,28 @@ export function ProgramsRoot(): React.ReactElement {
|
||||
time. Your progress will be saved and you can continue later.
|
||||
</Typography>
|
||||
|
||||
{programs.map((program) => {
|
||||
const create = program.create;
|
||||
if (create === null) return <></>;
|
||||
<Box sx={{ display: 'grid', width: 'fit-content' }}>
|
||||
{programs.map((program) => {
|
||||
const create = program.create;
|
||||
if (create === null) return <></>;
|
||||
|
||||
return (
|
||||
<React.Fragment key={program.name}>
|
||||
<Tooltip title={create.tooltip}>
|
||||
<Button
|
||||
sx={{ my: 1 }}
|
||||
onClick={(event) => {
|
||||
if (!event.isTrusted) return;
|
||||
player.startCreateProgramWork(router, program.name, create.time, create.level);
|
||||
}}
|
||||
>
|
||||
{program.name}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<br />
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
return (
|
||||
<React.Fragment key={program.name}>
|
||||
<Tooltip title={create.tooltip}>
|
||||
<Button
|
||||
sx={{ my: 1 }}
|
||||
onClick={(event) => {
|
||||
if (!event.isTrusted) return;
|
||||
player.startCreateProgramWork(router, program.name, create.time, create.level);
|
||||
}}
|
||||
>
|
||||
{program.name}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -43,10 +43,10 @@ class BitburnerSaveObject {
|
||||
StaneksGiftSave = "";
|
||||
SaveTimestamp = "";
|
||||
|
||||
getSaveString(): string {
|
||||
getSaveString(excludeRunningScripts = false): string {
|
||||
this.PlayerSave = JSON.stringify(Player);
|
||||
|
||||
this.AllServersSave = saveAllServers();
|
||||
this.AllServersSave = saveAllServers(excludeRunningScripts);
|
||||
this.CompaniesSave = JSON.stringify(Companies);
|
||||
this.FactionsSave = JSON.stringify(Factions);
|
||||
this.AliasesSave = JSON.stringify(Aliases);
|
||||
@ -68,7 +68,7 @@ class BitburnerSaveObject {
|
||||
}
|
||||
|
||||
saveGame(emitToastEvent = true): void {
|
||||
const saveString = this.getSaveString();
|
||||
const saveString = this.getSaveString(Settings.ExcludeRunningScriptsFromSave);
|
||||
|
||||
save(saveString)
|
||||
.then(() => {
|
||||
@ -80,7 +80,7 @@ class BitburnerSaveObject {
|
||||
}
|
||||
|
||||
exportGame(): void {
|
||||
const saveString = this.getSaveString();
|
||||
const saveString = this.getSaveString(Settings.ExcludeRunningScriptsFromSave);
|
||||
|
||||
// Save file name is based on current timestamp and BitNode
|
||||
const epochTime = Math.round(Date.now() / 1000);
|
||||
|
@ -118,7 +118,7 @@ export class Script {
|
||||
*/
|
||||
saveScript(player: IPlayer, filename: string, code: string, hostname: string, otherScripts: Script[]): void {
|
||||
// Update code and filename
|
||||
this.code = code.replace(/^\s+|\s+$/g, "");
|
||||
this.code = Script.formatCode(code);
|
||||
|
||||
this.filename = filename;
|
||||
this.server = hostname;
|
||||
@ -158,6 +158,15 @@ export class Script {
|
||||
s.rehash();
|
||||
return s;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats code: Removes the starting & trailing whitespace
|
||||
* @param {string} code - The code to format
|
||||
* @returns The formatted code
|
||||
*/
|
||||
static formatCode(code: string): string {
|
||||
return code.replace(/^\s+|\s+$/g, "");
|
||||
}
|
||||
}
|
||||
|
||||
Reviver.constructors.Script = Script;
|
||||
|
73
src/ScriptEditor/NetscriptDefinitions.d.ts
vendored
73
src/ScriptEditor/NetscriptDefinitions.d.ts
vendored
@ -109,6 +109,32 @@ interface RunningScript {
|
||||
threads: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface of a netscript port
|
||||
* @public
|
||||
*/
|
||||
export interface IPort {
|
||||
/** write data to the port and removes and returns first element if full */
|
||||
write: (value: any) => any;
|
||||
/** add data to port if not full.
|
||||
* @returns true if added and false if full and not added */
|
||||
tryWrite: (value: any) => boolean;
|
||||
/** reads and removes first element from port
|
||||
* if no data in port returns "NULL PORT DATA"
|
||||
*/
|
||||
read: () => any;
|
||||
/** reads first element without removing it from port
|
||||
* if no data in port returns "NULL PORT DATA"
|
||||
*/
|
||||
peek: () => any;
|
||||
/** check if port is full */
|
||||
full: () => boolean;
|
||||
/** check if port is empty */
|
||||
empty: () => boolean;
|
||||
/** removes all data from port */
|
||||
clear: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Data representing the internal values of a crime.
|
||||
* @public
|
||||
@ -1776,7 +1802,7 @@ export interface Singularity {
|
||||
* guarantee that your browser will follow that time limit.
|
||||
*
|
||||
* @param crime - Name of crime to attempt.
|
||||
* @returns True if you successfully start working on the specified program, and false otherwise.
|
||||
* @returns The number of milliseconds it takes to attempt the specified crime.
|
||||
*/
|
||||
commitCrime(crime: string): number;
|
||||
|
||||
@ -3854,6 +3880,36 @@ interface UserInterface {
|
||||
* RAM cost: cost: 0 GB
|
||||
*/
|
||||
resetTheme(): void;
|
||||
|
||||
/**
|
||||
* Get the current styles
|
||||
* @remarks
|
||||
* RAM cost: cost: 0 GB
|
||||
*
|
||||
* @returns An object containing the player's styles
|
||||
*/
|
||||
getStyles(): IStyleSettings;
|
||||
|
||||
/**
|
||||
* Sets the current styles
|
||||
* @remarks
|
||||
* RAM cost: cost: 0 GB
|
||||
* @example
|
||||
* Usage example (NS2)
|
||||
* ```ts
|
||||
* const styles = ns.ui.getStyles();
|
||||
* styles.fontFamily = 'Comic Sans Ms';
|
||||
* ns.ui.setStyles(styles);
|
||||
* ```
|
||||
*/
|
||||
setStyles(newStyles: IStyleSettings): void;
|
||||
|
||||
/**
|
||||
* Resets the player's styles to the default values
|
||||
* @remarks
|
||||
* RAM cost: cost: 0 GB
|
||||
*/
|
||||
resetStyles(): void;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -5409,7 +5465,7 @@ export interface NS extends Singularity {
|
||||
* @param port - Port number. Must be an integer between 1 and 20.
|
||||
* @returns Data in the specified port.
|
||||
*/
|
||||
getPortHandle(port: number): any[];
|
||||
getPortHandle(port: number): IPort;
|
||||
|
||||
/**
|
||||
* Delete a file.
|
||||
@ -5604,7 +5660,7 @@ export interface NS extends Singularity {
|
||||
* @param args - Formating arguments.
|
||||
* @returns Formated text.
|
||||
*/
|
||||
sprintf(format: string, ...args: string[]): string;
|
||||
sprintf(format: string, ...args: any[]): string;
|
||||
|
||||
/**
|
||||
* Format a string with an array of arguments.
|
||||
@ -5616,7 +5672,7 @@ export interface NS extends Singularity {
|
||||
* @param args - Formating arguments.
|
||||
* @returns Formated text.
|
||||
*/
|
||||
vsprintf(format: string, args: string[]): string;
|
||||
vsprintf(format: string, args: any[]): string;
|
||||
|
||||
/**
|
||||
* Format a number
|
||||
@ -6328,3 +6384,12 @@ interface UserInterfaceTheme {
|
||||
backgroundsecondary: string;
|
||||
button: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface Styles
|
||||
* @internal
|
||||
*/
|
||||
interface IStyleSettings {
|
||||
fontFamily: string;
|
||||
lineHeight: number;
|
||||
}
|
||||
|
@ -686,7 +686,9 @@ export function Root(props: IProps): React.ReactElement {
|
||||
const serverScript = server.scripts.find((s) => s.filename === openScript.fileName);
|
||||
if (serverScript === undefined) return " *";
|
||||
|
||||
return serverScript.code !== openScript.code ? " *" : "";
|
||||
// The server code is stored with its starting & trailing whitespace removed
|
||||
const openScriptFormatted = Script.formatCode(openScript.code);
|
||||
return serverScript.code !== openScriptFormatted ? " *" : "";
|
||||
}
|
||||
|
||||
// Toolbars are roughly 112px:
|
||||
@ -846,7 +848,7 @@ export function Root(props: IProps): React.ReactElement {
|
||||
<span style={{ color: Settings.theme.primary, fontSize: "20px", textAlign: "center" }}>
|
||||
<Typography variant="h4">No open files</Typography>
|
||||
<Typography variant="h5">
|
||||
Use `nano FILENAME` in
|
||||
Use <code>nano FILENAME</code> in
|
||||
<br />
|
||||
the terminal to open files
|
||||
</Typography>
|
||||
|
@ -204,10 +204,14 @@ export function loadAllServers(saveString: string): void {
|
||||
AllServers = JSON.parse(saveString, Reviver);
|
||||
}
|
||||
|
||||
export function saveAllServers(): string {
|
||||
export function saveAllServers(excludeRunningScripts = false): string {
|
||||
const TempAllServers = JSON.parse(JSON.stringify(AllServers), Reviver);
|
||||
for (const key in TempAllServers) {
|
||||
const server = TempAllServers[key];
|
||||
if (excludeRunningScripts) {
|
||||
server.runningScripts = [];
|
||||
continue;
|
||||
}
|
||||
for (let i = 0; i < server.runningScripts.length; ++i) {
|
||||
const runningScriptObj = server.runningScripts[i];
|
||||
runningScriptObj.logs.length = 0;
|
||||
|
@ -1,8 +1,10 @@
|
||||
import { ISelfInitializer, ISelfLoading } from "../types";
|
||||
import { OwnedAugmentationsOrderSetting, PurchaseAugmentationsOrderSetting } from "./SettingEnums";
|
||||
import { defaultTheme, ITheme } from "./Themes";
|
||||
import { defaultStyles, IStyleSettings } from "./Styles";
|
||||
import { WordWrapOptions } from '../ScriptEditor/ui/Options';
|
||||
import { defaultStyles } from "./Styles";
|
||||
import { WordWrapOptions } from "../ScriptEditor/ui/Options";
|
||||
import { OverviewSettings } from "../ui/React/Overview";
|
||||
import { IStyleSettings } from "../ScriptEditor/NetscriptDefinitions";
|
||||
|
||||
/**
|
||||
* Represents the default settings the player could customize.
|
||||
@ -41,6 +43,11 @@ interface IDefaultSettings {
|
||||
*/
|
||||
DisableTextEffects: boolean;
|
||||
|
||||
/**
|
||||
* Whether overview progress bars should be visible.
|
||||
*/
|
||||
DisableOverviewProgressBars: boolean;
|
||||
|
||||
/**
|
||||
* Enable bash hotkeys
|
||||
*/
|
||||
@ -111,6 +118,11 @@ interface IDefaultSettings {
|
||||
*/
|
||||
SuppressSavedGameToast: boolean;
|
||||
|
||||
/*
|
||||
* Whether the game should skip saving the running scripts for late game
|
||||
*/
|
||||
ExcludeRunningScriptsFromSave: boolean;
|
||||
|
||||
/*
|
||||
* Theme colors
|
||||
*/
|
||||
@ -125,6 +137,11 @@ interface IDefaultSettings {
|
||||
* Use GiB instead of GB
|
||||
*/
|
||||
UseIEC60027_2: boolean;
|
||||
|
||||
/*
|
||||
* Character overview settings
|
||||
*/
|
||||
overview: OverviewSettings;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -160,6 +177,7 @@ export const defaultSettings: IDefaultSettings = {
|
||||
DisableASCIIArt: false,
|
||||
DisableHotkeys: false,
|
||||
DisableTextEffects: false,
|
||||
DisableOverviewProgressBars: false,
|
||||
EnableBashHotkeys: false,
|
||||
TimestampsFormat: "",
|
||||
Locale: "en",
|
||||
@ -175,9 +193,11 @@ export const defaultSettings: IDefaultSettings = {
|
||||
SuppressTIXPopup: false,
|
||||
SuppressSavedGameToast: false,
|
||||
UseIEC60027_2: false,
|
||||
ExcludeRunningScriptsFromSave: false,
|
||||
|
||||
theme: defaultTheme,
|
||||
styles: defaultStyles,
|
||||
overview: { x: 0, y: 0, opened: true },
|
||||
};
|
||||
|
||||
/**
|
||||
@ -192,6 +212,7 @@ export const Settings: ISettings & ISelfInitializer & ISelfLoading = {
|
||||
DisableASCIIArt: defaultSettings.DisableASCIIArt,
|
||||
DisableHotkeys: defaultSettings.DisableHotkeys,
|
||||
DisableTextEffects: defaultSettings.DisableTextEffects,
|
||||
DisableOverviewProgressBars: defaultSettings.DisableOverviewProgressBars,
|
||||
EnableBashHotkeys: defaultSettings.EnableBashHotkeys,
|
||||
TimestampsFormat: defaultSettings.TimestampsFormat,
|
||||
Locale: "en",
|
||||
@ -209,14 +230,16 @@ export const Settings: ISettings & ISelfInitializer & ISelfLoading = {
|
||||
SuppressTIXPopup: defaultSettings.SuppressTIXPopup,
|
||||
SuppressSavedGameToast: defaultSettings.SuppressSavedGameToast,
|
||||
UseIEC60027_2: defaultSettings.UseIEC60027_2,
|
||||
ExcludeRunningScriptsFromSave: defaultSettings.ExcludeRunningScriptsFromSave,
|
||||
MonacoTheme: "monokai",
|
||||
MonacoInsertSpaces: false,
|
||||
MonacoFontSize: 20,
|
||||
MonacoVim: false,
|
||||
MonacoWordWrap: 'off',
|
||||
MonacoWordWrap: "off",
|
||||
|
||||
theme: { ...defaultTheme },
|
||||
styles: { ...defaultStyles },
|
||||
overview: defaultSettings.overview,
|
||||
init() {
|
||||
Object.assign(Settings, defaultSettings);
|
||||
},
|
||||
@ -226,6 +249,8 @@ export const Settings: ISettings & ISelfInitializer & ISelfLoading = {
|
||||
delete save.theme;
|
||||
Object.assign(Settings.styles, save.styles);
|
||||
delete save.styles;
|
||||
Object.assign(Settings.overview, save.overview);
|
||||
delete save.overview;
|
||||
Object.assign(Settings, save);
|
||||
},
|
||||
};
|
||||
|
@ -1,9 +1,4 @@
|
||||
import React from "react";
|
||||
|
||||
export interface IStyleSettings {
|
||||
fontFamily: React.CSSProperties["fontFamily"];
|
||||
lineHeight: React.CSSProperties["lineHeight"];
|
||||
}
|
||||
import { IStyleSettings } from "../ScriptEditor/NetscriptDefinitions";
|
||||
|
||||
export const defaultStyles: IStyleSettings = {
|
||||
lineHeight: 1.5,
|
||||
|
@ -69,29 +69,29 @@ const TemplatedHelpTexts: IMap<(command: string) => string[]> = {
|
||||
|
||||
export const HelpTexts: IMap<string[]> = {
|
||||
alias: [
|
||||
'alias [-g] [name="value"] ',
|
||||
'Usage: alias [-g] [name="value"] ',
|
||||
" ",
|
||||
"Create or display aliases. An alias enables a replacement of a word with another string. ",
|
||||
"It can be used to abbreviate a commonly used command, or commonly used parts of a command. The NAME ",
|
||||
"of an alias defines the word that will be replaced, while the VALUE defines what it will be replaced by. For example, ",
|
||||
"you could create the alias 'nuke' for the Terminal command 'run NUKE.exe' using the following: ",
|
||||
" ",
|
||||
'alias nuke="run NUKE.exe"',
|
||||
' alias nuke="run NUKE.exe"',
|
||||
" ",
|
||||
"Then, to run the NUKE.exe program you would just have to enter 'nuke' in Terminal rather than the full command. ",
|
||||
"It is important to note that 'default' aliases will only be substituted for the first word of a Terminal command. For ",
|
||||
"example, if the following alias was set: ",
|
||||
" ",
|
||||
'alias worm="HTTPWorm.exe"',
|
||||
' alias worm="HTTPWorm.exe"',
|
||||
" ",
|
||||
"and then you tried to run the following terminal command: ",
|
||||
" ",
|
||||
"run worm",
|
||||
" run worm",
|
||||
" ",
|
||||
"This would fail because the worm alias is not the first word of a Terminal command. To allow an alias to be substituted ",
|
||||
"anywhere in a Terminal command, rather than just the first word, you must set it to be a global alias using the -g flag: ",
|
||||
" ",
|
||||
'alias -g worm="HTTPWorm.exe"',
|
||||
' alias -g worm="HTTPWorm.exe"',
|
||||
" ",
|
||||
"Now, the 'worm' alias will be substituted anytime it shows up as an individual word in a Terminal command. ",
|
||||
" ",
|
||||
@ -102,15 +102,16 @@ export const HelpTexts: IMap<string[]> = {
|
||||
" ",
|
||||
],
|
||||
analyze: [
|
||||
"analyze",
|
||||
"Usage: analyze",
|
||||
" ",
|
||||
"Prints details and statistics about the current server. The information that is printed includes basic ",
|
||||
"server details such as the hostname, whether the player has root access, what ports are opened/closed, and also ",
|
||||
"hacking-related information such as an estimated chance to successfully hack, an estimate of how much money is ",
|
||||
"available on the server, etc.",
|
||||
" ",
|
||||
],
|
||||
backdoor: [
|
||||
"backdoor",
|
||||
"Usage: backdoor",
|
||||
" ",
|
||||
"Install a backdoor on the current machine, grants a secret bonus depending on the machine.",
|
||||
" ",
|
||||
@ -118,7 +119,7 @@ export const HelpTexts: IMap<string[]> = {
|
||||
" ",
|
||||
],
|
||||
buy: [
|
||||
"buy [-l / -a / program]",
|
||||
"Usage: buy [-l / -a / program]",
|
||||
" ",
|
||||
"Purchase a program through the Dark Web. Requires a TOR router to use.",
|
||||
" ",
|
||||
@ -128,65 +129,72 @@ export const HelpTexts: IMap<string[]> = {
|
||||
"If this command is ran with the '-a' flag, it will attempt to purchase all unowned programs.",
|
||||
" ",
|
||||
"Otherwise, the name of the program must be passed in as a parameter. This name is NOT case-sensitive.",
|
||||
" ",
|
||||
],
|
||||
cat: [
|
||||
"cat [file]",
|
||||
"Usage: cat [file]",
|
||||
" ",
|
||||
"Display message (.msg), literature (.lit), or text (.txt) files. Examples:",
|
||||
" ",
|
||||
"cat j1.msg",
|
||||
" cat j1.msg",
|
||||
" ",
|
||||
"cat foo.lit",
|
||||
" cat foo.lit",
|
||||
" ",
|
||||
" cat servers.txt",
|
||||
" ",
|
||||
"cat servers.txt",
|
||||
],
|
||||
cd: [
|
||||
"cd [dir]",
|
||||
"Usage: cd [dir]",
|
||||
" ",
|
||||
"Change to the specified directory. Note that this works even for directories that don't exist. If you ",
|
||||
"change to a directory that does not exist, it will not be 'created'. Examples:",
|
||||
" ",
|
||||
"cd scripts/hacking",
|
||||
" cd scripts/hacking",
|
||||
" ",
|
||||
"cd /logs",
|
||||
" cd /logs",
|
||||
" ",
|
||||
" cd ../",
|
||||
" ",
|
||||
"cd ../",
|
||||
],
|
||||
check: [
|
||||
"check [script name] [args...]",
|
||||
"Usage: check [script name] [args...]",
|
||||
" ",
|
||||
"Print the logs of the script specified by the script name and arguments to the Terminal. Each argument must be separated by ",
|
||||
"a space. Remember that a running script is uniquely ",
|
||||
"identified both by its name and the arguments that are used to start it. So, if a script was ran with the following arguments: ",
|
||||
" ",
|
||||
"run foo.script 1 2 foodnstuff",
|
||||
" run foo.script 1 2 foodnstuff",
|
||||
" ",
|
||||
"Then to run the 'check' command on this script you would have to pass the same arguments in: ",
|
||||
" ",
|
||||
"check foo.script 1 2 foodnstuff",
|
||||
" check foo.script 1 2 foodnstuff",
|
||||
" ",
|
||||
],
|
||||
clear: [
|
||||
"clear",
|
||||
"Usage: clear",
|
||||
" ",
|
||||
"Clear the Terminal screen, deleting all of the text. Note that this does not delete the user's command history, so using the up ",
|
||||
"and down arrow keys is still valid. Also note that this is permanent and there is no way to undo this. Synonymous with 'cls' command",
|
||||
" ",
|
||||
],
|
||||
cls: [
|
||||
"cls",
|
||||
"Usage: cls",
|
||||
" ",
|
||||
"Clear the Terminal screen, deleting all of the text. Note that this does not delete the user's command history, so using the up ",
|
||||
"and down arrow keys is still valid. Also note that this is permanent and there is no way to undo this. Synonymous with 'clear' command",
|
||||
" ",
|
||||
],
|
||||
connect: [
|
||||
"connect [hostname]",
|
||||
"Usage: connect [hostname]",
|
||||
" ",
|
||||
"Connect to a remote server. The hostname or IP address of the remote server must be given as the argument ",
|
||||
"to this command. Note that only servers that are immediately adjacent to the current server in the network can be connected to. To ",
|
||||
"see which servers can be connected to, use the 'scan' command.",
|
||||
" ",
|
||||
],
|
||||
cp: ["cp [src] [dst]", " ", "Copy a file on this server. To copy a file to another server use scp."],
|
||||
cp: ["Usage: cp [src] [dst]", " ", "Copy a file on this server. To copy a file to another server use scp.", " "],
|
||||
download: [
|
||||
"download [script/text file]",
|
||||
"Usage: download [script/text file]",
|
||||
" ",
|
||||
"Downloads a script or text file to your computer (like your real life computer).",
|
||||
" ",
|
||||
@ -200,7 +208,7 @@ export const HelpTexts: IMap<string[]> = {
|
||||
" ",
|
||||
],
|
||||
expr: [
|
||||
"expr [mathematical expression]",
|
||||
"Usage: expr [mathematical expression]",
|
||||
" ",
|
||||
"Evaluate a simple mathematical expression. Supports native JavaScript operators:",
|
||||
" ",
|
||||
@ -208,47 +216,49 @@ export const HelpTexts: IMap<string[]> = {
|
||||
" ",
|
||||
"Example:",
|
||||
" ",
|
||||
"expr 25 * 2 ** 10",
|
||||
" expr 25 * 2 ** 10",
|
||||
" ",
|
||||
"Note that letters (non-digits) are not allowed and will be removed from the input.",
|
||||
" ",
|
||||
],
|
||||
free: [
|
||||
"free",
|
||||
"Usage: free",
|
||||
" ",
|
||||
"Displays the memory usage on the current machine. Print the amount of RAM that is available on the current server as well as ",
|
||||
"how much of it is being used.",
|
||||
" ",
|
||||
],
|
||||
grow: [
|
||||
"grow",
|
||||
"",
|
||||
"Usage: grow",
|
||||
" ",
|
||||
"Spoof transactions in the current server. Increasing the money available by hacking. Requires root access.",
|
||||
"See the wiki page for hacking mechanics.",
|
||||
" ",
|
||||
],
|
||||
hack: [
|
||||
"hack",
|
||||
"Usage: hack",
|
||||
" ",
|
||||
"Attempt to hack the current server. Requires root access in order to be run. See the wiki page for hacking mechanics",
|
||||
" ",
|
||||
],
|
||||
help: [
|
||||
"help [command]",
|
||||
"Usage: help [command]",
|
||||
" ",
|
||||
"Display Terminal help information. Without arguments, 'help' prints a list of all valid Terminal commands and a brief ",
|
||||
"description of their functionality. You can also pass the name of a Terminal command as an argument to 'help' to print ",
|
||||
"more detailed information about the Terminal command. Examples: ",
|
||||
" ",
|
||||
"help alias",
|
||||
" help alias",
|
||||
" ",
|
||||
" help scan-analyze",
|
||||
" ",
|
||||
"help scan-analyze",
|
||||
],
|
||||
home: [
|
||||
"home" + "Connect to your home computer. This will work no matter what server you are currently connected to.",
|
||||
"Usage: home", " ", "Connect to your home computer. This will work no matter what server you are currently connected to.", " ",
|
||||
],
|
||||
hostname: ["hostname", " ", "Prints the hostname of the current server"],
|
||||
hostname: ["Usage: hostname", " ", "Prints the hostname of the current server", " "],
|
||||
kill: [
|
||||
"kill [script name] [args...]",
|
||||
" ",
|
||||
"kill [pid]",
|
||||
"Usage: kill [script name] [args...] or kill [pid",
|
||||
" ",
|
||||
"Kill the script specified by the script name and arguments OR by its PID.",
|
||||
" ",
|
||||
@ -257,24 +267,26 @@ export const HelpTexts: IMap<string[]> = {
|
||||
"uniquely identified by both its name and the arguments that are used to start ",
|
||||
"it. So, if a script was ran with the following arguments:",
|
||||
" ",
|
||||
"run foo.script 1 sigma-cosmetics",
|
||||
" run foo.script 1 sigma-cosmetics",
|
||||
" ",
|
||||
"Then to kill this script the same arguments would have to be used:",
|
||||
" ",
|
||||
"kill foo.script 1 sigma-cosmetics",
|
||||
" kill foo.script 1 sigma-cosmetics",
|
||||
" ",
|
||||
"If you are killing the script using its PID, then the PID argument must be numeric",
|
||||
" ",
|
||||
],
|
||||
killall: [
|
||||
"killall",
|
||||
"Usage: killall",
|
||||
" ",
|
||||
"Kills all scripts on the current server. ",
|
||||
"Note that after the 'kill' command is issued for a script, it may take a while for the script to actually stop running. ",
|
||||
"This will happen if the script is in the middle of a command such as grow() or weaken() that takes time to execute. ",
|
||||
"The script will not be stopped/killed until after that time has elapsed.",
|
||||
" ",
|
||||
],
|
||||
ls: [
|
||||
"ls [dir] [| grep pattern]",
|
||||
"Usage: ls [dir] [| grep pattern]",
|
||||
" ",
|
||||
"The ls command, with no arguments, prints all files and directories on the current server's directory to the Terminal screen. ",
|
||||
"The files will be displayed in alphabetical order. ",
|
||||
@ -287,34 +299,36 @@ export const HelpTexts: IMap<string[]> = {
|
||||
" ",
|
||||
"List all files with the '.script' extension in the current directory:",
|
||||
" ",
|
||||
"ls | grep .script",
|
||||
" ls | grep .script",
|
||||
" ",
|
||||
"List all files with the '.js' extension in the root directory:",
|
||||
" ",
|
||||
"ls / | grep .js",
|
||||
" ls / | grep .js",
|
||||
" ",
|
||||
"List all files with the word 'purchase' in the filename, in the 'scripts' directory:",
|
||||
" ",
|
||||
"ls scripts | grep purchase",
|
||||
" ls scripts | grep purchase",
|
||||
" ",
|
||||
],
|
||||
lscpu: ["lscpu", " ", "Prints the number of CPU Cores the current server has"],
|
||||
lscpu: ["Usage: lscpu", " ", "Prints the number of CPU Cores the current server has", " "],
|
||||
|
||||
mem: [
|
||||
"mem [script name] [-t num_threads]",
|
||||
"Usage: mem [script name] [-t num_threads]",
|
||||
" ",
|
||||
"Displays the amount of RAM needed to run the specified script with a single thread. The command can also be used to print ",
|
||||
"the amount of RAM needed to run a script with multiple threads using the '-t' flag. If the '-t' flag is specified, then ",
|
||||
"an argument for the number of threads must be passed in afterwards. Examples:",
|
||||
" ",
|
||||
"mem foo.script",
|
||||
" mem foo.script",
|
||||
" ",
|
||||
"mem foo.script -t 50",
|
||||
" mem foo.script -t 50",
|
||||
" ",
|
||||
"The first example above will print the amount of RAM needed to run 'foo.script' with a single thread. The second example ",
|
||||
"above will print the amount of RAM needed to run 'foo.script' with 50 threads.",
|
||||
" ",
|
||||
],
|
||||
mv: [
|
||||
"mv [src] [dest]",
|
||||
"Usage: mv [src] [dest]",
|
||||
" ",
|
||||
"Move the source file to the specified destination. This can also be used to rename files. ",
|
||||
"This command only works for scripts and text files (.txt). This command CANNOT be used to ",
|
||||
@ -324,22 +338,23 @@ export const HelpTexts: IMap<string[]> = {
|
||||
"full filepath. ",
|
||||
"Examples: ",
|
||||
" ",
|
||||
"mv hacking-controller.script scripts/hacking-controller.script",
|
||||
" mv hacking-controller.script scripts/hacking-controller.script",
|
||||
" ",
|
||||
" mv myScript.js myOldScript.js",
|
||||
" ",
|
||||
"mv myScript.js myOldScript.js",
|
||||
],
|
||||
nano: TemplatedHelpTexts.scriptEditor('nano'),
|
||||
ps: ["ps", " ", "Prints all scripts that are running on the current server"],
|
||||
|
||||
ps: ["Usage: ps", " ", "Prints all scripts that are running on the current server", " "],
|
||||
rm: [
|
||||
"rm [file]",
|
||||
"Usage: rm [file]",
|
||||
" ",
|
||||
"Removes the specified file from the current server. A file can be a script, a program, or a message file. ",
|
||||
" ",
|
||||
"WARNING: This is permanent and cannot be undone",
|
||||
" ",
|
||||
],
|
||||
run: [
|
||||
"run [file name] [-t] [num threads] [args...]",
|
||||
"Usage: run [file name] [-t] [num threads] [args...]",
|
||||
" ",
|
||||
"Execute a program, script or coding contract.",
|
||||
" ",
|
||||
@ -354,13 +369,14 @@ export const HelpTexts: IMap<string[]> = {
|
||||
" ",
|
||||
],
|
||||
scan: [
|
||||
"scan",
|
||||
"Usage: scan",
|
||||
" ",
|
||||
"Prints all immediately-available network connection. This will print a list of all servers that you can currently connect ",
|
||||
"to using the 'connect' Terminal command.",
|
||||
" ",
|
||||
],
|
||||
"scan-analyze": [
|
||||
"scan-analyze [depth] [-a]",
|
||||
"Usage: scan-analyze [depth] [-a]",
|
||||
" ",
|
||||
"Prints detailed information about all servers up to [depth] nodes away on the network. Calling ",
|
||||
"'scan-analyze 1' will display information for the same servers that are shown by the 'scan' Terminal ",
|
||||
@ -376,65 +392,70 @@ export const HelpTexts: IMap<string[]> = {
|
||||
" ",
|
||||
"By default, this command will not display servers that you have purchased. However, you can pass in the ",
|
||||
"-a flag at the end of the command if you would like to enable that.",
|
||||
" ",
|
||||
],
|
||||
scp: [
|
||||
"scp [filename ...] [target server]",
|
||||
"Usage: scp [filename ...] [target server]",
|
||||
" ",
|
||||
"Copies the specified file(s) from the current server to the target server. ",
|
||||
"This command only works for script files (.script or .js extension), literature files (.lit extension), ",
|
||||
"and text files (.txt extension). ",
|
||||
"The second argument passed in must be the hostname or IP of the target server. Examples:",
|
||||
" ",
|
||||
"scp foo.script n00dles",
|
||||
" scp foo.script n00dles",
|
||||
" ",
|
||||
"scp foo.script bar.script n00dles",
|
||||
" scp foo.script bar.script n00dles",
|
||||
" ",
|
||||
],
|
||||
sudov: ["sudov", " ", "Prints whether or not you have root access to the current machine"],
|
||||
sudov: ["Usage: sudov", " ", "Prints whether or not you have root access to the current machine", " "],
|
||||
|
||||
tail: [
|
||||
"tail [script name] [args...]",
|
||||
"Usage: tail [script name] [args...]",
|
||||
" ",
|
||||
"Displays dynamic logs for the script specified by the script name and arguments. Each argument must be separated ",
|
||||
"by a space. Remember that a running script is uniquely identified by both its name and the arguments that were used ",
|
||||
"to run it. So, if a script was ran with the following arguments: ",
|
||||
" ",
|
||||
"run foo.script 10 50000",
|
||||
" run foo.script 10 50000",
|
||||
" ",
|
||||
"Then in order to check its logs with 'tail' the same arguments must be used: ",
|
||||
" ",
|
||||
"tail foo.script 10 50000",
|
||||
" tail foo.script 10 50000",
|
||||
" ",
|
||||
],
|
||||
top: [
|
||||
"top",
|
||||
"Usage: top",
|
||||
" ",
|
||||
"Prints a list of all scripts running on the current server as well as their thread count and how much ",
|
||||
"RAM they are using in total.",
|
||||
" ",
|
||||
],
|
||||
unalias: [
|
||||
"unalias [alias name]",
|
||||
"Usage: unalias [alias name]",
|
||||
" ",
|
||||
"Deletes the specified alias. Note that the double quotation marks are required. ",
|
||||
" ",
|
||||
"As an example, if an alias was declared using:",
|
||||
" ",
|
||||
'alias r="run"',
|
||||
' alias r="run"',
|
||||
" ",
|
||||
"Then it could be removed using:",
|
||||
" ",
|
||||
"unalias r",
|
||||
" unalias r",
|
||||
" ",
|
||||
"It is not necessary to differentiate between global and non-global aliases when using 'unalias'",
|
||||
" ",
|
||||
],
|
||||
vim: TemplatedHelpTexts.scriptEditor('vim'),
|
||||
weaken: [
|
||||
"weaken",
|
||||
"",
|
||||
"Usage: weaken",
|
||||
" ",
|
||||
"Reduces the security level of the current server. Decreasing the time it takes for all operations on this server.",
|
||||
"Requires root access. See the wiki page for hacking mechanics.",
|
||||
" ",
|
||||
],
|
||||
wget: [
|
||||
"wget [url] [target file]",
|
||||
"Usage: wget [url] [target file]",
|
||||
" ",
|
||||
"Retrieves data from a URL and downloads it to a file on the current server. The data can only ",
|
||||
"be downloaded to a script (.script, .ns, .js) or a text file (.txt). If the file already exists, ",
|
||||
@ -443,6 +464,7 @@ export const HelpTexts: IMap<string[]> = {
|
||||
"Note that it will not be possible to download data from many websites because they do not allow ",
|
||||
"cross-origin resource sharing (CORS). Example:",
|
||||
" ",
|
||||
"wget https://raw.githubusercontent.com/danielyxie/bitburner/master/README.md game_readme.txt",
|
||||
" wget https://raw.githubusercontent.com/danielyxie/bitburner/master/README.md game_readme.txt",
|
||||
" ",
|
||||
],
|
||||
};
|
||||
|
@ -315,6 +315,7 @@ export class Terminal implements ITerminal {
|
||||
this.print("Root Access: " + (hasAdminRights ? "YES" : "NO"));
|
||||
this.print("Can run scripts on this host: " + (hasAdminRights ? "YES" : "NO"));
|
||||
if (currServ instanceof Server) {
|
||||
this.print("Backdoor: " + (currServ.backdoorInstalled ? "YES" : "NO"));
|
||||
const hackingSkill = currServ.requiredHackingSkill;
|
||||
this.print("Required hacking skill for hack() and backdoor: " + (!isHacknet ? hackingSkill : "N/A"));
|
||||
const security = currServ.hackDifficulty;
|
||||
|
@ -5,8 +5,6 @@ import { BaseServer } from "../../Server/BaseServer";
|
||||
import { getServerOnNetwork } from "../../Server/ServerHelpers";
|
||||
import { GetServer } from "../../Server/AllServers";
|
||||
import { Server } from "../../Server/Server";
|
||||
import { Programs } from "src/Programs/Programs";
|
||||
import { programsMetadata } from "src/Programs/data/ProgramsMetadata";
|
||||
|
||||
export function connect(
|
||||
terminal: ITerminal,
|
||||
|
@ -3,6 +3,7 @@ import { IRouter } from "../../ui/Router";
|
||||
import { IPlayer } from "../../PersonObjects/IPlayer";
|
||||
import { BaseServer } from "../../Server/BaseServer";
|
||||
import { numeralWrapper } from "../../ui/numeralFormat";
|
||||
import { Settings } from "../../Settings/Settings";
|
||||
|
||||
export function mem(
|
||||
terminal: ITerminal,
|
||||
@ -40,8 +41,9 @@ export function mem(
|
||||
);
|
||||
|
||||
const verboseEntries = script.ramUsageEntries?.sort((a, b) => b.cost - a.cost) ?? [];
|
||||
const padding = Settings.UseIEC60027_2 ? 9 : 8;
|
||||
for (const entry of verboseEntries) {
|
||||
terminal.print(`${numeralWrapper.formatRAM(entry.cost * numThreads).padStart(8)} | ${entry.name} (${entry.type})`);
|
||||
terminal.print(`${numeralWrapper.formatRAM(entry.cost * numThreads).padStart(padding)} | ${entry.name} (${entry.type})`);
|
||||
}
|
||||
|
||||
if (ramUsage > 0 && verboseEntries.length === 0) {
|
||||
|
@ -41,7 +41,7 @@ export function runScript(
|
||||
|
||||
// Check if this script is already running
|
||||
if (findRunningScript(scriptName, args, server) != null) {
|
||||
terminal.error("This script is already running. Cannot run multiple instances");
|
||||
terminal.error("This script is already running with the same args. Cannot run multiple instances with the same args");
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -6,6 +6,7 @@ import { DarkWebItems } from "../DarkWeb/DarkWebItems";
|
||||
import { IPlayer } from "../PersonObjects/IPlayer";
|
||||
import { GetServer, GetAllServers } from "../Server/AllServers";
|
||||
import { ParseCommand, ParseCommands } from "./Parser";
|
||||
import { HelpTexts } from "./HelpText";
|
||||
import { isScriptFilename } from "../Script/isScriptFilename";
|
||||
import { compile } from "../NetscriptJSEvaluator";
|
||||
import { Flags } from "../NetscriptFunctions/Flags";
|
||||
@ -178,26 +179,8 @@ export async function determineAllPossibilitiesForTabCompletion(
|
||||
return input.startsWith(t_cmd);
|
||||
}
|
||||
|
||||
/**
|
||||
* If the command starts with './' and the index == -1, then the user
|
||||
* has input ./partialexecutablename so autocomplete the script or program.
|
||||
* Put './' in front of each script/executable
|
||||
*/
|
||||
if (isCommand("./") && index == -1) {
|
||||
//All programs and scripts
|
||||
for (let i = 0; i < currServ.scripts.length; ++i) {
|
||||
allPos.push("./" + currServ.scripts[i].filename);
|
||||
}
|
||||
|
||||
//Programs are on home computer
|
||||
for (let i = 0; i < homeComputer.programs.length; ++i) {
|
||||
allPos.push("./" + homeComputer.programs[i]);
|
||||
}
|
||||
return allPos;
|
||||
}
|
||||
|
||||
// Autocomplete the command
|
||||
if (index === -1) {
|
||||
if (index === -1 && !input.startsWith('./')) {
|
||||
return commands.concat(Object.keys(Aliases)).concat(Object.keys(GlobalAliases));
|
||||
}
|
||||
|
||||
@ -286,14 +269,22 @@ export async function determineAllPossibilitiesForTabCompletion(
|
||||
}
|
||||
|
||||
async function scriptAutocomplete(): Promise<string[] | undefined> {
|
||||
if (!isCommand("run") && !isCommand("tail") && !isCommand("kill")) return;
|
||||
const commands = ParseCommands(input);
|
||||
if (!isCommand("run") && !isCommand("tail") && !isCommand("kill") && !input.startsWith("./")) return;
|
||||
let copy = input;
|
||||
if (input.startsWith("./")) copy = "run " + input.slice(2);
|
||||
const commands = ParseCommands(copy);
|
||||
if (commands.length === 0) return;
|
||||
const command = ParseCommand(commands[commands.length - 1]);
|
||||
const filename = command[1] + "";
|
||||
if (!isScriptFilename(filename)) return; // Not a script.
|
||||
if (filename.endsWith(".script")) return; // Doesn't work with ns1.
|
||||
const script = currServ.scripts.find((script) => script.filename === filename);
|
||||
// Use regex to remove any leading './', and then check if it matches against
|
||||
// the output of processFilepath or if it matches with a '/' prepended,
|
||||
// this way autocomplete works inside of directories
|
||||
const script = currServ.scripts.find((script) => {
|
||||
const fn = filename.replace(/^\.\//g, '');
|
||||
return (processFilepath(script.filename) === fn || script.filename === '/' + fn);
|
||||
})
|
||||
if (!script) return; // Doesn't exist.
|
||||
if (!script.module) {
|
||||
await compile(p, script, currServ.scripts);
|
||||
@ -335,6 +326,35 @@ export async function determineAllPossibilitiesForTabCompletion(
|
||||
const pos = await scriptAutocomplete();
|
||||
if (pos) return pos;
|
||||
|
||||
// If input starts with './', essentially treat it as a slimmer
|
||||
// invocation of `run`.
|
||||
if (input.startsWith("./")) {
|
||||
// All programs and scripts
|
||||
for (const script of currServ.scripts) {
|
||||
const res = processFilepath(script.filename);
|
||||
if (res) {
|
||||
allPos.push(res);
|
||||
}
|
||||
}
|
||||
|
||||
for (const program of currServ.programs) {
|
||||
const res = processFilepath(program);
|
||||
if (res) {
|
||||
allPos.push(res);
|
||||
}
|
||||
}
|
||||
|
||||
// All coding contracts
|
||||
for (const cct of currServ.contracts) {
|
||||
const res = processFilepath(cct.fn);
|
||||
if (res) {
|
||||
allPos.push(res);
|
||||
}
|
||||
}
|
||||
|
||||
return allPos;
|
||||
}
|
||||
|
||||
if (isCommand("run")) {
|
||||
addAllScripts();
|
||||
addAllPrograms();
|
||||
@ -377,5 +397,11 @@ export async function determineAllPossibilitiesForTabCompletion(
|
||||
addAllDirectories();
|
||||
}
|
||||
|
||||
if (isCommand("help")) {
|
||||
// Get names from here instead of commands array because some
|
||||
// undocumented/nonexistent commands are in the array
|
||||
return Object.keys(HelpTexts);
|
||||
}
|
||||
|
||||
return allPos;
|
||||
}
|
||||
|
@ -95,20 +95,42 @@ export function TerminalRoot({ terminal, router, player }: IProps): React.ReactE
|
||||
setKey((key) => key + 1);
|
||||
}
|
||||
|
||||
useEffect(() => TerminalEvents.subscribe(_.debounce(async () => rerender(), 25, { maxWait: 50 })), []);
|
||||
useEffect(() => TerminalClearEvents.subscribe(_.debounce(async () => clear(), 25, { maxWait: 50 })), []);
|
||||
useEffect(() => {
|
||||
const debounced = _.debounce(async () => rerender(), 25, { maxWait: 50 });
|
||||
const unsubscribe = TerminalEvents.subscribe(debounced);
|
||||
return () => {
|
||||
debounced.cancel();
|
||||
unsubscribe();
|
||||
}
|
||||
}, []);
|
||||
|
||||
function doScroll(): void {
|
||||
useEffect(() => {
|
||||
const debounced = _.debounce(async () => clear(), 25, { maxWait: 50 });
|
||||
const unsubscribe = TerminalClearEvents.subscribe(debounced);
|
||||
return () => {
|
||||
debounced.cancel();
|
||||
unsubscribe();
|
||||
}
|
||||
}, []);
|
||||
|
||||
function doScroll(): number | undefined {
|
||||
const hook = scrollHook.current;
|
||||
if (hook !== null) {
|
||||
setTimeout(() => hook.scrollIntoView(true), 50);
|
||||
return window.setTimeout(() => hook.scrollIntoView(true), 50);
|
||||
}
|
||||
}
|
||||
|
||||
doScroll();
|
||||
|
||||
useEffect(() => {
|
||||
setTimeout(doScroll, 50);
|
||||
let scrollId: number;
|
||||
const id = setTimeout(() => {
|
||||
scrollId = doScroll() ?? 0;
|
||||
}, 50);
|
||||
return () => {
|
||||
clearTimeout(id);
|
||||
clearTimeout(scrollId);
|
||||
}
|
||||
}, []);
|
||||
|
||||
function lineClass(s: string): string {
|
||||
|
@ -24,7 +24,7 @@ function getDB(): Promise<IDBObjectStore> {
|
||||
indexedDbRequest.onsuccess = function (this: IDBRequest<IDBDatabase>) {
|
||||
const db = this.result;
|
||||
if (!db) {
|
||||
reject("database loadign result was undefined");
|
||||
reject("database loading result was undefined");
|
||||
return;
|
||||
}
|
||||
resolve(db.transaction(["savestring"], "readwrite").objectStore("savestring"));
|
||||
|
@ -52,6 +52,11 @@
|
||||
</script>
|
||||
<% } %>
|
||||
<style>
|
||||
html, body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: 100%;
|
||||
}
|
||||
body {
|
||||
background-color: black;
|
||||
}
|
||||
|
@ -5,6 +5,8 @@ import { TTheme as Theme, ThemeEvents, refreshTheme } from "./ui/React/Theme";
|
||||
import { LoadingScreen } from "./ui/LoadingScreen";
|
||||
import { initElectron } from "./Electron";
|
||||
initElectron();
|
||||
globalThis["React"] = React;
|
||||
globalThis["ReactDOM"] = ReactDOM;
|
||||
ReactDOM.render(
|
||||
<Theme>
|
||||
<LoadingScreen />
|
||||
|
@ -77,7 +77,6 @@ import { enterBitNode } from "../RedPill";
|
||||
import { Context } from "./Context";
|
||||
import { RecoveryMode, RecoveryRoot } from "./React/RecoveryRoot";
|
||||
import { AchievementsRoot } from "../Achievements/AchievementsRoot";
|
||||
import { Settings } from "../Settings/Settings";
|
||||
|
||||
const htmlLocation = location;
|
||||
|
||||
@ -93,6 +92,11 @@ const useStyles = makeStyles((theme: Theme) =>
|
||||
"-ms-overflow-style": "none" /* for Internet Explorer, Edge */,
|
||||
"scrollbar-width": "none" /* for Firefox */,
|
||||
margin: theme.spacing(0),
|
||||
flexGrow: 1,
|
||||
display: "block",
|
||||
padding: "8px",
|
||||
minHeight: "100vh",
|
||||
boxSizing: 'border-box',
|
||||
},
|
||||
}),
|
||||
);
|
||||
@ -187,7 +191,7 @@ export let Router: IRouter = {
|
||||
},
|
||||
toAchievements: () => {
|
||||
throw new Error("Router called before initialization");
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
function determineStartPage(player: IPlayer): Page {
|
||||
@ -198,7 +202,7 @@ function determineStartPage(player: IPlayer): Page {
|
||||
|
||||
export function GameRoot({ player, engine, terminal }: IProps): React.ReactElement {
|
||||
const classes = useStyles();
|
||||
const [{files, vim}, setEditorOptions] = useState({files: {}, vim: false})
|
||||
const [{ files, vim }, setEditorOptions] = useState({ files: {}, vim: false });
|
||||
const [page, setPage] = useState(determineStartPage(player));
|
||||
const setRerender = useState(0)[1];
|
||||
const [faction, setFaction] = useState<Faction>(
|
||||
@ -315,7 +319,7 @@ export function GameRoot({ player, engine, terminal }: IProps): React.ReactEleme
|
||||
mainPage = <BitverseRoot flume={flume} enter={enterBitNode} quick={quick} />;
|
||||
withSidebar = false;
|
||||
withPopups = false;
|
||||
break
|
||||
break;
|
||||
}
|
||||
case Page.Infiltration: {
|
||||
mainPage = <InfiltrationRoot location={location} />;
|
||||
@ -351,13 +355,15 @@ export function GameRoot({ player, engine, terminal }: IProps): React.ReactEleme
|
||||
break;
|
||||
}
|
||||
case Page.ScriptEditor: {
|
||||
mainPage = <ScriptEditorRoot
|
||||
files={files}
|
||||
hostname={player.getCurrentServer().hostname}
|
||||
player={player}
|
||||
router={Router}
|
||||
vim={vim}
|
||||
/>;
|
||||
mainPage = (
|
||||
<ScriptEditorRoot
|
||||
files={files}
|
||||
hostname={player.getCurrentServer().hostname}
|
||||
player={player}
|
||||
router={Router}
|
||||
vim={vim}
|
||||
/>
|
||||
);
|
||||
break;
|
||||
}
|
||||
case Page.ActiveScripts: {
|
||||
@ -385,13 +391,15 @@ export function GameRoot({ player, engine, terminal }: IProps): React.ReactEleme
|
||||
break;
|
||||
}
|
||||
case Page.Tutorial: {
|
||||
mainPage = <TutorialRoot
|
||||
reactivateTutorial={() => {
|
||||
prestigeAugmentation();
|
||||
Router.toTerminal();
|
||||
iTutorialStart();
|
||||
}}
|
||||
/>;
|
||||
mainPage = (
|
||||
<TutorialRoot
|
||||
reactivateTutorial={() => {
|
||||
prestigeAugmentation();
|
||||
Router.toTerminal();
|
||||
iTutorialStart();
|
||||
}}
|
||||
/>
|
||||
);
|
||||
break;
|
||||
}
|
||||
case Page.DevMenu: {
|
||||
@ -419,59 +427,65 @@ export function GameRoot({ player, engine, terminal }: IProps): React.ReactEleme
|
||||
break;
|
||||
}
|
||||
case Page.StockMarket: {
|
||||
mainPage = <StockMarketRoot
|
||||
buyStockLong={buyStock}
|
||||
buyStockShort={shortStock}
|
||||
cancelOrder={cancelOrder}
|
||||
eventEmitterForReset={eventEmitterForUiReset}
|
||||
initStockMarket={initStockMarketFnForReact}
|
||||
p={player}
|
||||
placeOrder={placeOrder}
|
||||
sellStockLong={sellStock}
|
||||
sellStockShort={sellShort}
|
||||
stockMarket={StockMarket}
|
||||
/>;
|
||||
mainPage = (
|
||||
<StockMarketRoot
|
||||
buyStockLong={buyStock}
|
||||
buyStockShort={shortStock}
|
||||
cancelOrder={cancelOrder}
|
||||
eventEmitterForReset={eventEmitterForUiReset}
|
||||
initStockMarket={initStockMarketFnForReact}
|
||||
p={player}
|
||||
placeOrder={placeOrder}
|
||||
sellStockLong={sellStock}
|
||||
sellStockShort={sellShort}
|
||||
stockMarket={StockMarket}
|
||||
/>
|
||||
);
|
||||
break;
|
||||
}
|
||||
case Page.City: {
|
||||
mainPage = <LocationCity />;
|
||||
break;
|
||||
}
|
||||
case Page.Job:
|
||||
case Page.Job:
|
||||
case Page.Location: {
|
||||
mainPage = <GenericLocation loc={location} />;
|
||||
break;
|
||||
}
|
||||
case Page.Options: {
|
||||
mainPage = <GameOptionsRoot
|
||||
player={player}
|
||||
save={() => saveObject.saveGame()}
|
||||
export={() => {
|
||||
// Apply the export bonus before saving the game
|
||||
onExport(player);
|
||||
saveObject.exportGame()
|
||||
}}
|
||||
forceKill={killAllScripts}
|
||||
softReset={() => {
|
||||
dialogBoxCreate("Soft Reset!");
|
||||
prestigeAugmentation();
|
||||
Router.toTerminal();
|
||||
}}
|
||||
/>;
|
||||
mainPage = (
|
||||
<GameOptionsRoot
|
||||
player={player}
|
||||
save={() => saveObject.saveGame()}
|
||||
export={() => {
|
||||
// Apply the export bonus before saving the game
|
||||
onExport(player);
|
||||
saveObject.exportGame();
|
||||
}}
|
||||
forceKill={killAllScripts}
|
||||
softReset={() => {
|
||||
dialogBoxCreate("Soft Reset!");
|
||||
prestigeAugmentation();
|
||||
Router.toTerminal();
|
||||
}}
|
||||
/>
|
||||
);
|
||||
break;
|
||||
}
|
||||
case Page.Augmentations: {
|
||||
mainPage = <AugmentationsRoot
|
||||
exportGameFn={() => {
|
||||
// Apply the export bonus before saving the game
|
||||
onExport(player);
|
||||
saveObject.exportGame();
|
||||
}}
|
||||
installAugmentationsFn={() => {
|
||||
installAugmentations();
|
||||
Router.toTerminal();
|
||||
}}
|
||||
/>;
|
||||
mainPage = (
|
||||
<AugmentationsRoot
|
||||
exportGameFn={() => {
|
||||
// Apply the export bonus before saving the game
|
||||
onExport(player);
|
||||
saveObject.exportGame();
|
||||
}}
|
||||
installAugmentationsFn={() => {
|
||||
installAugmentations();
|
||||
Router.toTerminal();
|
||||
}}
|
||||
/>
|
||||
);
|
||||
break;
|
||||
}
|
||||
case Page.Achievements: {
|
||||
@ -479,12 +493,12 @@ export function GameRoot({ player, engine, terminal }: IProps): React.ReactEleme
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<Context.Player.Provider value={player}>
|
||||
<Context.Router.Provider value={Router}>
|
||||
<SnackbarProvider>
|
||||
<Overview>
|
||||
<Overview mode={ITutorial.isRunning ? "tutorial" : "overview"}>
|
||||
{!ITutorial.isRunning ? (
|
||||
<CharacterOverview save={() => saveObject.saveGame()} killScripts={killAllScripts} />
|
||||
) : (
|
||||
@ -494,19 +508,19 @@ export function GameRoot({ player, engine, terminal }: IProps): React.ReactEleme
|
||||
{withSidebar ? (
|
||||
<Box display="flex" flexDirection="row" width="100%">
|
||||
<SidebarRoot player={player} router={Router} page={page} />
|
||||
<Box className={classes.root} flexGrow={1} display="block" px={1} min-height="100vh">
|
||||
{mainPage}
|
||||
</Box>
|
||||
<Box className={classes.root}>{mainPage}</Box>
|
||||
</Box>
|
||||
) : mainPage }
|
||||
) : (
|
||||
<Box className={classes.root}>{mainPage}</Box>
|
||||
)}
|
||||
<Unclickable />
|
||||
{withPopups && (
|
||||
<>
|
||||
<LogBoxManager />
|
||||
<AlertManager />
|
||||
<PromptManager />
|
||||
<InvitationModal />
|
||||
<Snackbar />
|
||||
<LogBoxManager />
|
||||
<AlertManager />
|
||||
<PromptManager />
|
||||
<InvitationModal />
|
||||
<Snackbar />
|
||||
</>
|
||||
)}
|
||||
</SnackbarProvider>
|
||||
|
@ -12,7 +12,6 @@ import { CopyableText } from "../React/CopyableText";
|
||||
import ListItem from "@mui/material/ListItem";
|
||||
import EqualizerIcon from "@mui/icons-material/Equalizer";
|
||||
import LastPageIcon from "@mui/icons-material/LastPage";
|
||||
import VisibilityOffIcon from "@mui/icons-material/VisibilityOff";
|
||||
import HelpIcon from "@mui/icons-material/Help";
|
||||
import AccountTreeIcon from "@mui/icons-material/AccountTree";
|
||||
import StorageIcon from "@mui/icons-material/Storage";
|
||||
@ -61,7 +60,7 @@ export function InteractiveTutorialRoot(): React.ReactElement {
|
||||
This tutorial will show you the basics of the game. You may skip the tutorial at any time.
|
||||
<br />
|
||||
<br />
|
||||
You can also click the eye symbol <VisibilityOffIcon /> to temporarily hide this tutorial.
|
||||
You can also collapse this panel to temporarily hide this tutorial.
|
||||
</Typography>
|
||||
</>
|
||||
),
|
||||
|
@ -7,6 +7,7 @@ import createStyles from "@mui/styles/createStyles";
|
||||
import { numeralWrapper } from "../../ui/numeralFormat";
|
||||
import { Reputation } from "./Reputation";
|
||||
import { KillScriptsModal } from "./KillScriptsModal";
|
||||
import { convertTimeMsToTimeElapsedString } from "../../utils/StringHelperFunctions";
|
||||
|
||||
import Table from "@mui/material/Table";
|
||||
import TableBody from "@mui/material/TableBody";
|
||||
@ -23,6 +24,9 @@ import { use } from "../Context";
|
||||
import { StatsProgressOverviewCell } from "./StatsProgressBar";
|
||||
import { BitNodeMultipliers } from "../../BitNode/BitNodeMultipliers";
|
||||
|
||||
import { Box, Tooltip } from "@mui/material";
|
||||
import { CONSTANTS } from "../../Constants";
|
||||
|
||||
interface IProps {
|
||||
save: () => void;
|
||||
killScripts: () => void;
|
||||
@ -74,95 +78,39 @@ function Bladeburner(): React.ReactElement {
|
||||
);
|
||||
}
|
||||
|
||||
function Work(): React.ReactElement {
|
||||
const player = use.Player();
|
||||
const router = use.Router();
|
||||
interface WorkInProgressOverviewProps {
|
||||
tooltip: React.ReactNode;
|
||||
header: React.ReactNode;
|
||||
children: React.ReactNode;
|
||||
onClickFocus: () => void;
|
||||
}
|
||||
|
||||
function WorkInProgressOverview({
|
||||
tooltip,
|
||||
children,
|
||||
onClickFocus,
|
||||
header,
|
||||
}: WorkInProgressOverviewProps): React.ReactElement {
|
||||
const classes = useStyles();
|
||||
if (!player.isWorking || player.focus) return <></>;
|
||||
|
||||
if (player.className !== "") {
|
||||
return (
|
||||
<>
|
||||
<TableRow>
|
||||
<TableCell component="th" scope="row" colSpan={2} classes={{ root: classes.cellNone }}>
|
||||
<Typography>Work in progress:</Typography>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell component="th" scope="row" colSpan={2} classes={{ root: classes.cellNone }}>
|
||||
<Typography>{player.className}</Typography>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell component="th" scope="row" align="center" colSpan={2} classes={{ root: classes.cellNone }}>
|
||||
<Button
|
||||
onClick={() => {
|
||||
player.startFocusing();
|
||||
router.toWork();
|
||||
}}
|
||||
>
|
||||
Focus
|
||||
</Button>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
if (player.createProgramName !== "") {
|
||||
return (
|
||||
<>
|
||||
<TableRow>
|
||||
<TableCell component="th" scope="row" colSpan={2} classes={{ root: classes.cellNone }}>
|
||||
<Typography>Work in progress:</Typography>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell component="th" scope="row" colSpan={2} classes={{ root: classes.cellNone }}>
|
||||
<Typography>
|
||||
{player.createProgramName}{" "}
|
||||
{((player.timeWorkedCreateProgram / player.timeNeededToCompleteWork) * 100).toFixed(2)}%
|
||||
</Typography>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell component="th" scope="row" align="center" colSpan={2} classes={{ root: classes.cellNone }}>
|
||||
<Button
|
||||
onClick={() => {
|
||||
player.startFocusing();
|
||||
router.toWork();
|
||||
}}
|
||||
>
|
||||
Focus
|
||||
</Button>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<TableRow>
|
||||
<TableCell component="th" scope="row" colSpan={2} classes={{ root: classes.cellNone }}>
|
||||
<Typography>Work in progress:</Typography>
|
||||
<TableCell component="th" scope="row" colSpan={2} classes={{ root: classes.workCell }}>
|
||||
<Tooltip title={<>{tooltip}</>}>
|
||||
<Typography className={classes.workHeader} sx={{ pt: 1, pb: 0.5 }}>
|
||||
{header}
|
||||
</Typography>
|
||||
</Tooltip>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell component="th" scope="row" colSpan={2} classes={{ root: classes.cellNone }}>
|
||||
<Typography>
|
||||
+<Reputation reputation={player.workRepGained} /> rep
|
||||
</Typography>
|
||||
<TableCell component="th" scope="row" colSpan={2} classes={{ root: classes.workCell }}>
|
||||
<Typography className={classes.workSubtitles}>{children}</Typography>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell component="th" scope="row" align="center" colSpan={2} classes={{ root: classes.cellNone }}>
|
||||
<Button
|
||||
onClick={() => {
|
||||
player.startFocusing();
|
||||
router.toWork();
|
||||
}}
|
||||
>
|
||||
<Button sx={{ mt: 1 }} onClick={onClickFocus}>
|
||||
Focus
|
||||
</Button>
|
||||
</TableCell>
|
||||
@ -171,8 +119,91 @@ function Work(): React.ReactElement {
|
||||
);
|
||||
}
|
||||
|
||||
function Work(): React.ReactElement {
|
||||
const player = use.Player();
|
||||
const router = use.Router();
|
||||
const onClickFocus = (): void => {
|
||||
player.startFocusing();
|
||||
router.toWork();
|
||||
};
|
||||
|
||||
if (!player.isWorking || player.focus) return <></>;
|
||||
|
||||
let details = <></>;
|
||||
let header = <></>;
|
||||
let innerText = <></>;
|
||||
if (player.workType === CONSTANTS.WorkTypeCompanyPartTime || player.workType === CONSTANTS.WorkTypeCompany) {
|
||||
details = (
|
||||
<>
|
||||
{player.jobs[player.companyName]} at <strong>{player.companyName}</strong>
|
||||
</>
|
||||
);
|
||||
header = (
|
||||
<>
|
||||
Working at <strong>{player.companyName}</strong>
|
||||
</>
|
||||
);
|
||||
innerText = (
|
||||
<>
|
||||
+<Reputation reputation={player.workRepGained} /> rep
|
||||
</>
|
||||
);
|
||||
} else if (player.workType === CONSTANTS.WorkTypeFaction) {
|
||||
details = (
|
||||
<>
|
||||
{player.factionWorkType} for <strong>{player.currentWorkFactionName}</strong>
|
||||
</>
|
||||
);
|
||||
header = (
|
||||
<>
|
||||
Working for <strong>{player.currentWorkFactionName}</strong>
|
||||
</>
|
||||
);
|
||||
innerText = (
|
||||
<>
|
||||
+<Reputation reputation={player.workRepGained} /> rep
|
||||
</>
|
||||
);
|
||||
} else if (player.workType === CONSTANTS.WorkTypeStudyClass) {
|
||||
details = <>{player.workType}</>;
|
||||
header = <>You are {player.className}</>;
|
||||
innerText = <>{convertTimeMsToTimeElapsedString(player.timeWorked)}</>;
|
||||
} else if (player.workType === CONSTANTS.WorkTypeCreateProgram) {
|
||||
details = <>Coding {player.createProgramName}</>;
|
||||
header = <>Creating a program</>;
|
||||
innerText = (
|
||||
<>
|
||||
{player.createProgramName}{" "}
|
||||
{((player.timeWorkedCreateProgram / player.timeNeededToCompleteWork) * 100).toFixed(2)}%
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<WorkInProgressOverview tooltip={details} header={header} onClickFocus={onClickFocus}>
|
||||
{innerText}
|
||||
</WorkInProgressOverview>
|
||||
);
|
||||
}
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) =>
|
||||
createStyles({
|
||||
workCell: {
|
||||
textAlign: "center",
|
||||
maxWidth: "200px",
|
||||
borderBottom: "none",
|
||||
padding: 0,
|
||||
margin: 0,
|
||||
},
|
||||
|
||||
workHeader: {
|
||||
fontSize: "0.9rem",
|
||||
},
|
||||
|
||||
workSubtitles: {
|
||||
fontSize: "0.8rem",
|
||||
},
|
||||
|
||||
cellNone: {
|
||||
borderBottom: "none",
|
||||
padding: 0,
|
||||
@ -287,7 +318,9 @@ export function CharacterOverview({ save, killScripts }: IProps): React.ReactEle
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<StatsProgressOverviewCell progress={hackingProgress} color={theme.colors.hack} />
|
||||
{!Settings.DisableOverviewProgressBars && (
|
||||
<StatsProgressOverviewCell progress={hackingProgress} color={theme.colors.hack} />
|
||||
)}
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell component="th" scope="row" classes={{ root: classes.cell }}>
|
||||
@ -314,7 +347,9 @@ export function CharacterOverview({ save, killScripts }: IProps): React.ReactEle
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<StatsProgressOverviewCell progress={strengthProgress} color={theme.colors.combat} />
|
||||
{!Settings.DisableOverviewProgressBars && (
|
||||
<StatsProgressOverviewCell progress={strengthProgress} color={theme.colors.combat} />
|
||||
)}
|
||||
</TableRow>
|
||||
|
||||
<TableRow>
|
||||
@ -331,7 +366,9 @@ export function CharacterOverview({ save, killScripts }: IProps): React.ReactEle
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<StatsProgressOverviewCell progress={defenseProgress} color={theme.colors.combat} />
|
||||
{!Settings.DisableOverviewProgressBars && (
|
||||
<StatsProgressOverviewCell progress={defenseProgress} color={theme.colors.combat} />
|
||||
)}
|
||||
</TableRow>
|
||||
|
||||
<TableRow>
|
||||
@ -348,7 +385,9 @@ export function CharacterOverview({ save, killScripts }: IProps): React.ReactEle
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<StatsProgressOverviewCell progress={dexterityProgress} color={theme.colors.combat} />
|
||||
{!Settings.DisableOverviewProgressBars && (
|
||||
<StatsProgressOverviewCell progress={dexterityProgress} color={theme.colors.combat} />
|
||||
)}
|
||||
</TableRow>
|
||||
|
||||
<TableRow>
|
||||
@ -365,7 +404,9 @@ export function CharacterOverview({ save, killScripts }: IProps): React.ReactEle
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<StatsProgressOverviewCell progress={agilityProgress} color={theme.colors.combat} />
|
||||
{!Settings.DisableOverviewProgressBars && (
|
||||
<StatsProgressOverviewCell progress={agilityProgress} color={theme.colors.combat} />
|
||||
)}
|
||||
</TableRow>
|
||||
|
||||
<TableRow>
|
||||
@ -382,7 +423,9 @@ export function CharacterOverview({ save, killScripts }: IProps): React.ReactEle
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<StatsProgressOverviewCell progress={charismaProgress} color={theme.colors.cha} />
|
||||
{!Settings.DisableOverviewProgressBars && (
|
||||
<StatsProgressOverviewCell progress={charismaProgress} color={theme.colors.cha} />
|
||||
)}
|
||||
</TableRow>
|
||||
|
||||
<Intelligence />
|
||||
@ -406,21 +449,24 @@ export function CharacterOverview({ save, killScripts }: IProps): React.ReactEle
|
||||
</TableRow>
|
||||
<Work />
|
||||
<Bladeburner />
|
||||
|
||||
<TableRow>
|
||||
<TableCell align="center" classes={{ root: classes.cellNone }}>
|
||||
<IconButton onClick={save}>
|
||||
<SaveIcon color={Settings.AutosaveInterval !== 0 ? "primary" : "error"} />
|
||||
</IconButton>
|
||||
</TableCell>
|
||||
<TableCell align="center" classes={{ root: classes.cellNone }}>
|
||||
<IconButton onClick={() => setKillOpen(true)}>
|
||||
<ClearAllIcon color="error" />
|
||||
</IconButton>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
<Box sx={{ display: "flex", borderTop: `1px solid ${Settings.theme.welllight}` }}>
|
||||
<Box sx={{ display: "flex", flex: 1, justifyContent: "flex-start", alignItems: "center" }}>
|
||||
<IconButton onClick={save}>
|
||||
<Tooltip title="Save game">
|
||||
<SaveIcon color={Settings.AutosaveInterval !== 0 ? "primary" : "error"} />
|
||||
</Tooltip>
|
||||
</IconButton>
|
||||
</Box>
|
||||
<Box sx={{ display: "flex", flex: 1, justifyContent: "flex-end", alignItems: "center" }}>
|
||||
<IconButton onClick={() => setKillOpen(true)}>
|
||||
<Tooltip title="Kill all running scripts">
|
||||
<ClearAllIcon color="error" />
|
||||
</Tooltip>
|
||||
</IconButton>
|
||||
</Box>
|
||||
</Box>
|
||||
<KillScriptsModal open={killOpen} onClose={() => setKillOpen(false)} killScripts={killScripts} />
|
||||
</>
|
||||
);
|
||||
|
@ -25,20 +25,22 @@ export function CorruptableText(props: IProps): JSX.Element {
|
||||
|
||||
useEffect(() => {
|
||||
let counter = 5;
|
||||
const id = setInterval(() => {
|
||||
const timers: number[] = [];
|
||||
const intervalId = setInterval(() => {
|
||||
counter--;
|
||||
if (counter > 0) return;
|
||||
counter = Math.random() * 5;
|
||||
const index = Math.random() * content.length;
|
||||
const letter = content.charAt(index);
|
||||
setContent((content) => replace(content, index, randomize(letter)));
|
||||
setTimeout(() => {
|
||||
timers.push(window.setTimeout(() => {
|
||||
setContent((content) => replace(content, index, letter));
|
||||
}, 500);
|
||||
}, 500));
|
||||
}, 20);
|
||||
|
||||
return () => {
|
||||
clearInterval(id);
|
||||
clearInterval(intervalId);
|
||||
timers.forEach((timerId) => clearTimeout(timerId));
|
||||
};
|
||||
}, []);
|
||||
|
||||
|
@ -8,8 +8,6 @@ import createStyles from "@mui/styles/createStyles";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import Slider from "@mui/material/Slider";
|
||||
import Grid from "@mui/material/Grid";
|
||||
import FormControlLabel from "@mui/material/FormControlLabel";
|
||||
import Switch from "@mui/material/Switch";
|
||||
import Select, { SelectChangeEvent } from "@mui/material/Select";
|
||||
import MenuItem from "@mui/material/MenuItem";
|
||||
import Button from "@mui/material/Button";
|
||||
@ -35,6 +33,7 @@ import { SnackbarEvents } from "./Snackbar";
|
||||
import { Settings } from "../../Settings/Settings";
|
||||
import { save, deleteGame } from "../../db";
|
||||
import { formatTime } from "../../utils/helpers/formatTime";
|
||||
import { OptionSwitch } from "./OptionSwitch";
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) =>
|
||||
createStyles({
|
||||
@ -68,27 +67,8 @@ export function GameOptionsRoot(props: IProps): React.ReactElement {
|
||||
const [logSize, setLogSize] = useState(Settings.MaxLogCapacity);
|
||||
const [portSize, setPortSize] = useState(Settings.MaxPortCapacity);
|
||||
const [terminalSize, setTerminalSize] = useState(Settings.MaxTerminalCapacity);
|
||||
|
||||
const [autosaveInterval, setAutosaveInterval] = useState(Settings.AutosaveInterval);
|
||||
|
||||
const [suppressMessages, setSuppressMessages] = useState(Settings.SuppressMessages);
|
||||
const [suppressFactionInvites, setSuppressFactionInvites] = useState(Settings.SuppressFactionInvites);
|
||||
const [suppressTravelConfirmations, setSuppressTravelConfirmations] = useState(Settings.SuppressTravelConfirmation);
|
||||
const [suppressBuyAugmentationConfirmation, setSuppressBuyAugmentationConfirmation] = useState(
|
||||
Settings.SuppressBuyAugmentationConfirmation,
|
||||
);
|
||||
const [suppressTIXPopup, setSuppressTIXPopup] = useState(Settings.SuppressTIXPopup);
|
||||
const [suppressBladeburnerPopup, setSuppressBladeburnerPopup] = useState(Settings.SuppressBladeburnerPopup);
|
||||
const [suppressSavedGameToast, setSuppresSavedGameToast] = useState(Settings.SuppressSavedGameToast);
|
||||
|
||||
const [disableHotkeys, setDisableHotkeys] = useState(Settings.DisableHotkeys);
|
||||
const [disableASCIIArt, setDisableASCIIArt] = useState(Settings.DisableASCIIArt);
|
||||
const [disableTextEffects, setDisableTextEffects] = useState(Settings.DisableTextEffects);
|
||||
const [enableBashHotkeys, setEnableBashHotkeys] = useState(Settings.EnableBashHotkeys);
|
||||
const [timestampFormat, setTimestampFormat] = useState(Settings.TimestampsFormat);
|
||||
const [saveGameOnFileSave, setSaveGameOnFileSave] = useState(Settings.SaveGameOnFileSave);
|
||||
const [useIEC60027_2, setUseIEC60027_2] = useState(Settings.UseIEC60027_2);
|
||||
|
||||
const [locale, setLocale] = useState(Settings.Locale);
|
||||
const [diagnosticOpen, setDiagnosticOpen] = useState(false);
|
||||
const [deleteGameOpen, setDeleteOpen] = useState(false);
|
||||
@ -123,76 +103,15 @@ export function GameOptionsRoot(props: IProps): React.ReactElement {
|
||||
Settings.AutosaveInterval = newValue as number;
|
||||
}
|
||||
|
||||
function handleSuppressMessagesChange(event: React.ChangeEvent<HTMLInputElement>): void {
|
||||
setSuppressMessages(event.target.checked);
|
||||
Settings.SuppressMessages = event.target.checked;
|
||||
}
|
||||
|
||||
function handleSuppressFactionInvitesChange(event: React.ChangeEvent<HTMLInputElement>): void {
|
||||
setSuppressFactionInvites(event.target.checked);
|
||||
Settings.SuppressFactionInvites = event.target.checked;
|
||||
}
|
||||
|
||||
function handleSuppressTravelConfirmationsChange(event: React.ChangeEvent<HTMLInputElement>): void {
|
||||
setSuppressTravelConfirmations(event.target.checked);
|
||||
Settings.SuppressTravelConfirmation = event.target.checked;
|
||||
}
|
||||
|
||||
function handleSuppressBuyAugmentationConfirmationChange(event: React.ChangeEvent<HTMLInputElement>): void {
|
||||
setSuppressBuyAugmentationConfirmation(event.target.checked);
|
||||
Settings.SuppressBuyAugmentationConfirmation = event.target.checked;
|
||||
}
|
||||
|
||||
function handleSuppressTIXPopupChange(event: React.ChangeEvent<HTMLInputElement>): void {
|
||||
setSuppressTIXPopup(event.target.checked);
|
||||
Settings.SuppressTIXPopup = event.target.checked;
|
||||
}
|
||||
|
||||
function handleSuppressBladeburnerPopupChange(event: React.ChangeEvent<HTMLInputElement>): void {
|
||||
setSuppressBladeburnerPopup(event.target.checked);
|
||||
Settings.SuppressBladeburnerPopup = event.target.checked;
|
||||
}
|
||||
|
||||
function handleSuppressSavedGameToastChange(event: React.ChangeEvent<HTMLInputElement>): void {
|
||||
setSuppresSavedGameToast(event.target.checked);
|
||||
Settings.SuppressSavedGameToast = event.target.checked;
|
||||
}
|
||||
|
||||
function handleDisableHotkeysChange(event: React.ChangeEvent<HTMLInputElement>): void {
|
||||
setDisableHotkeys(event.target.checked);
|
||||
Settings.DisableHotkeys = event.target.checked;
|
||||
}
|
||||
|
||||
function handleDisableASCIIArtChange(event: React.ChangeEvent<HTMLInputElement>): void {
|
||||
setDisableASCIIArt(event.target.checked);
|
||||
Settings.DisableASCIIArt = event.target.checked;
|
||||
}
|
||||
function handleUseIEC60027_2Change(event: React.ChangeEvent<HTMLInputElement>): void {
|
||||
setUseIEC60027_2(event.target.checked);
|
||||
Settings.UseIEC60027_2 = event.target.checked;
|
||||
}
|
||||
|
||||
function handleDisableTextEffectsChange(event: React.ChangeEvent<HTMLInputElement>): void {
|
||||
setDisableTextEffects(event.target.checked);
|
||||
Settings.DisableTextEffects = event.target.checked;
|
||||
}
|
||||
function handleLocaleChange(event: SelectChangeEvent<string>): void {
|
||||
setLocale(event.target.value as string);
|
||||
Settings.Locale = event.target.value as string;
|
||||
}
|
||||
|
||||
function handleEnableBashHotkeysChange(event: React.ChangeEvent<HTMLInputElement>): void {
|
||||
setEnableBashHotkeys(event.target.checked);
|
||||
Settings.EnableBashHotkeys = event.target.checked;
|
||||
}
|
||||
function handleTimestampFormatChange(event: React.ChangeEvent<HTMLInputElement>): void {
|
||||
setTimestampFormat(event.target.value);
|
||||
Settings.TimestampsFormat = event.target.value;
|
||||
}
|
||||
function handleSaveGameOnFile(event: React.ChangeEvent<HTMLInputElement>): void {
|
||||
setSaveGameOnFileSave(event.target.checked);
|
||||
Settings.SaveGameOnFileSave = event.target.checked;
|
||||
}
|
||||
|
||||
function startImport(): void {
|
||||
if (!window.File || !window.FileReader || !window.FileList || !window.Blob) return;
|
||||
@ -392,200 +311,128 @@ export function GameOptionsRoot(props: IProps): React.ReactElement {
|
||||
/>
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<FormControlLabel
|
||||
control={<Switch checked={suppressMessages} onChange={handleSuppressMessagesChange} />}
|
||||
label={
|
||||
<Tooltip
|
||||
title={
|
||||
<Typography>
|
||||
If this is set, then any messages you receive will not appear as popups on the screen. They will
|
||||
still get sent to your home computer as '.msg' files and can be viewed with the 'cat' Terminal
|
||||
command.
|
||||
</Typography>
|
||||
}
|
||||
>
|
||||
<Typography>Suppress story messages</Typography>
|
||||
</Tooltip>
|
||||
}
|
||||
/>
|
||||
<OptionSwitch checked={Settings.SuppressMessages}
|
||||
onChange={(newValue) => Settings.SuppressMessages = newValue}
|
||||
text="Suppress story messages"
|
||||
tooltip={<>
|
||||
If this is set, then any messages you receive will not appear as popups on the screen. They will
|
||||
still get sent to your home computer as '.msg' files and can be viewed with the 'cat' Terminal
|
||||
command.
|
||||
</>} />
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<FormControlLabel
|
||||
control={<Switch checked={suppressFactionInvites} onChange={handleSuppressFactionInvitesChange} />}
|
||||
label={
|
||||
<Tooltip
|
||||
title={
|
||||
<Typography>
|
||||
If this is set, then any faction invites you receive will not appear as popups on the screen.
|
||||
Your outstanding faction invites can be viewed in the 'Factions' page.
|
||||
</Typography>
|
||||
}
|
||||
>
|
||||
<Typography>Suppress faction invites</Typography>
|
||||
</Tooltip>
|
||||
}
|
||||
/>
|
||||
<OptionSwitch checked={Settings.SuppressFactionInvites}
|
||||
onChange={(newValue) => Settings.SuppressFactionInvites = newValue}
|
||||
text="Suppress faction invites"
|
||||
tooltip={<>
|
||||
If this is set, then any faction invites you receive will not appear as popups on the screen.
|
||||
Your outstanding faction invites can be viewed in the 'Factions' page.
|
||||
</>} />
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Switch checked={suppressTravelConfirmations} onChange={handleSuppressTravelConfirmationsChange} />
|
||||
}
|
||||
label={
|
||||
<Tooltip
|
||||
title={
|
||||
<Typography>
|
||||
If this is set, the confirmation message before traveling will not show up. You will
|
||||
automatically be deducted the travel cost as soon as you click.
|
||||
</Typography>
|
||||
}
|
||||
>
|
||||
<Typography>Suppress travel confirmations</Typography>
|
||||
</Tooltip>
|
||||
}
|
||||
/>
|
||||
<OptionSwitch checked={Settings.SuppressTravelConfirmation}
|
||||
onChange={(newValue) => Settings.SuppressTravelConfirmation = newValue}
|
||||
text="Suppress travel confirmations"
|
||||
tooltip={<>
|
||||
If this is set, the confirmation message before traveling will not show up. You will
|
||||
automatically be deducted the travel cost as soon as you click.
|
||||
</>} />
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Switch
|
||||
checked={suppressBuyAugmentationConfirmation}
|
||||
onChange={handleSuppressBuyAugmentationConfirmationChange}
|
||||
/>
|
||||
}
|
||||
label={
|
||||
<Tooltip
|
||||
title={
|
||||
<Typography>
|
||||
If this is set, the confirmation message before buying augmentation will not show up.
|
||||
</Typography>
|
||||
}
|
||||
>
|
||||
<Typography>Suppress augmentations confirmation</Typography>
|
||||
</Tooltip>
|
||||
}
|
||||
/>
|
||||
<OptionSwitch checked={Settings.SuppressBuyAugmentationConfirmation}
|
||||
onChange={(newValue) => Settings.SuppressBuyAugmentationConfirmation = newValue}
|
||||
text="Suppress augmentations confirmation"
|
||||
tooltip={<>
|
||||
If this is set, the confirmation message before buying augmentation will not show up.
|
||||
</>} />
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<FormControlLabel
|
||||
control={<Switch checked={suppressTIXPopup} onChange={handleSuppressTIXPopupChange} />}
|
||||
label={
|
||||
<Tooltip
|
||||
title={<Typography>If this is set, the stock market will never create any popup.</Typography>}
|
||||
>
|
||||
<Typography>Suppress TIX messages</Typography>
|
||||
</Tooltip>
|
||||
}
|
||||
/>
|
||||
<OptionSwitch checked={Settings.SuppressTIXPopup}
|
||||
onChange={(newValue) => Settings.SuppressTIXPopup = newValue}
|
||||
text="Suppress TIX messages"
|
||||
tooltip={<>
|
||||
If this is set, the stock market will never create any popup.
|
||||
</>} />
|
||||
</ListItem>
|
||||
{!!props.player.bladeburner && (
|
||||
<ListItem>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Switch checked={suppressBladeburnerPopup} onChange={handleSuppressBladeburnerPopupChange} />
|
||||
}
|
||||
label={
|
||||
<Tooltip
|
||||
title={
|
||||
<Typography>
|
||||
If this is set, then having your Bladeburner actions interrupted by being busy with something
|
||||
else will not display a popup message.
|
||||
</Typography>
|
||||
}
|
||||
>
|
||||
<Typography>Suppress bladeburner popup</Typography>
|
||||
</Tooltip>
|
||||
}
|
||||
/>
|
||||
<OptionSwitch checked={Settings.SuppressBladeburnerPopup}
|
||||
onChange={(newValue) => Settings.SuppressBladeburnerPopup = newValue}
|
||||
text="Suppress bladeburner popup"
|
||||
tooltip={<>
|
||||
If this is set, then having your Bladeburner actions interrupted by being busy with something
|
||||
else will not display a popup message.
|
||||
</>} />
|
||||
</ListItem>
|
||||
)}
|
||||
<ListItem>
|
||||
<FormControlLabel
|
||||
control={<Switch checked={suppressSavedGameToast} onChange={handleSuppressSavedGameToastChange} />}
|
||||
label={
|
||||
<Tooltip
|
||||
title={
|
||||
<Typography>If this is set, there will be no "Game Saved!" toast appearing after an auto-save.</Typography>
|
||||
}
|
||||
>
|
||||
<Typography>Suppress Auto-Save Game Toast</Typography>
|
||||
</Tooltip>
|
||||
}
|
||||
/>
|
||||
<OptionSwitch checked={Settings.SuppressSavedGameToast}
|
||||
onChange={(newValue) => Settings.SuppressSavedGameToast = newValue}
|
||||
text="Suppress Auto-Save Game Toast"
|
||||
tooltip={<>
|
||||
If this is set, there will be no "Game Saved!" toast appearing after an auto-save.
|
||||
</>} />
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<FormControlLabel
|
||||
control={<Switch checked={disableHotkeys} onChange={handleDisableHotkeysChange} />}
|
||||
label={
|
||||
<Tooltip
|
||||
title={
|
||||
<Typography>
|
||||
If this is set, then most hotkeys (keyboard shortcuts) in the game are disabled. This includes
|
||||
Terminal commands, hotkeys to navigate between different parts of the game, and the "Save and
|
||||
Close (Ctrl + b)" hotkey in the Text Editor.
|
||||
</Typography>
|
||||
}
|
||||
>
|
||||
<Typography>Disable hotkeys</Typography>
|
||||
</Tooltip>
|
||||
}
|
||||
/>
|
||||
<OptionSwitch checked={Settings.DisableHotkeys}
|
||||
onChange={(newValue) => Settings.DisableHotkeys = newValue}
|
||||
text="Disable hotkeys"
|
||||
tooltip={<>
|
||||
If this is set, then most hotkeys (keyboard shortcuts) in the game are disabled. This includes
|
||||
Terminal commands, hotkeys to navigate between different parts of the game, and the "Save and
|
||||
Close (Ctrl + b)" hotkey in the Text Editor.
|
||||
</>} />
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<FormControlLabel
|
||||
control={<Switch checked={disableASCIIArt} onChange={handleDisableASCIIArtChange} />}
|
||||
label={
|
||||
<Tooltip title={<Typography>If this is set all ASCII art will be disabled.</Typography>}>
|
||||
<Typography>Disable ascii art</Typography>
|
||||
</Tooltip>
|
||||
}
|
||||
/>
|
||||
<OptionSwitch checked={Settings.DisableASCIIArt}
|
||||
onChange={(newValue) => Settings.DisableASCIIArt = newValue}
|
||||
text="Disable ascii art"
|
||||
tooltip={<>
|
||||
If this is set all ASCII art will be disabled.
|
||||
</>} />
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<FormControlLabel
|
||||
control={<Switch checked={disableTextEffects} onChange={handleDisableTextEffectsChange} />}
|
||||
label={
|
||||
<Tooltip
|
||||
title={
|
||||
<Typography>
|
||||
If this is set, text effects will not be displayed. This can help if text is difficult to read
|
||||
in certain areas.
|
||||
</Typography>
|
||||
}
|
||||
>
|
||||
<Typography>Disable text effects</Typography>
|
||||
</Tooltip>
|
||||
}
|
||||
/>
|
||||
</ListItem>
|
||||
|
||||
<ListItem>
|
||||
<FormControlLabel
|
||||
control={<Switch checked={enableBashHotkeys} onChange={handleEnableBashHotkeysChange} />}
|
||||
label={
|
||||
<Tooltip
|
||||
title={
|
||||
<Typography>
|
||||
Improved Bash emulation mode. Setting this to 1 enables several new Terminal shortcuts and
|
||||
features that more closely resemble a real Bash-style shell. Note that when this mode is
|
||||
enabled, the default browser shortcuts are overriden by the new Bash shortcuts.
|
||||
</Typography>
|
||||
}
|
||||
>
|
||||
<Typography>Enable bash hotkeys</Typography>
|
||||
</Tooltip>
|
||||
}
|
||||
/>
|
||||
<OptionSwitch checked={Settings.DisableTextEffects}
|
||||
onChange={(newValue) => Settings.DisableTextEffects = newValue}
|
||||
text="Disable text effects"
|
||||
tooltip={<>
|
||||
If this is set, text effects will not be displayed. This can help if text is difficult to read
|
||||
in certain areas.
|
||||
</>} />
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<FormControlLabel
|
||||
control={<Switch checked={useIEC60027_2} onChange={handleUseIEC60027_2Change} />}
|
||||
label={
|
||||
<Tooltip title={<Typography>If this is set all references to memory will use GiB instead of GB, in accordance with IEC 60027-2.</Typography>}>
|
||||
<Typography>Use GiB instead of GB</Typography>
|
||||
</Tooltip>
|
||||
}
|
||||
/>
|
||||
<OptionSwitch checked={Settings.DisableOverviewProgressBars}
|
||||
onChange={(newValue) => Settings.DisableOverviewProgressBars = newValue}
|
||||
text="Disable Overview Progress Bars"
|
||||
tooltip={<>
|
||||
If this is set, the progress bars in the character overview will be hidden.
|
||||
</>} />
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<OptionSwitch checked={Settings.EnableBashHotkeys}
|
||||
onChange={(newValue) => Settings.EnableBashHotkeys = newValue}
|
||||
text="Enable bash hotkeys"
|
||||
tooltip={<>
|
||||
Improved Bash emulation mode. Setting this to 1 enables several new Terminal shortcuts and
|
||||
features that more closely resemble a real Bash-style shell. Note that when this mode is
|
||||
enabled, the default browser shortcuts are overriden by the new Bash shortcuts.
|
||||
</>} />
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<OptionSwitch checked={Settings.UseIEC60027_2}
|
||||
onChange={(newValue) => Settings.UseIEC60027_2 = newValue}
|
||||
text="Use GiB instead of GB"
|
||||
tooltip={<>
|
||||
If this is set all references to memory will use GiB instead of GB, in accordance with IEC 60027-2.
|
||||
</>} />
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<OptionSwitch checked={Settings.ExcludeRunningScriptsFromSave}
|
||||
onChange={(newValue) => Settings.ExcludeRunningScriptsFromSave = newValue}
|
||||
text="Exclude Running Scripts from Save"
|
||||
tooltip={<>
|
||||
If this is set, the save file will exclude all running scripts. This is only useful if your save is lagging a lot. You'll have to restart your script every time you launch the game.
|
||||
</>} />
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<Tooltip
|
||||
@ -620,16 +467,12 @@ export function GameOptionsRoot(props: IProps): React.ReactElement {
|
||||
</ListItem>
|
||||
|
||||
<ListItem>
|
||||
<FormControlLabel
|
||||
control={<Switch checked={saveGameOnFileSave} onChange={handleSaveGameOnFile} />}
|
||||
label={
|
||||
<Tooltip
|
||||
title={<Typography>Save your game any time a file is saved in the script editor.</Typography>}
|
||||
>
|
||||
<Typography>Save game on file save</Typography>
|
||||
</Tooltip>
|
||||
}
|
||||
/>
|
||||
<OptionSwitch checked={Settings.SaveGameOnFileSave}
|
||||
onChange={(newValue) => Settings.SaveGameOnFileSave = newValue}
|
||||
text="Save game on file save"
|
||||
tooltip={<>
|
||||
Save your game any time a file is saved in the script editor.
|
||||
</>} />
|
||||
</ListItem>
|
||||
|
||||
<ListItem>
|
||||
@ -686,19 +529,19 @@ export function GameOptionsRoot(props: IProps): React.ReactElement {
|
||||
</>
|
||||
)}
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={6}>
|
||||
<Box>
|
||||
<Box sx={{ display: 'grid', width: 'fit-content', height: 'fit-content' }}>
|
||||
<Box sx={{ display: 'grid', gridTemplateColumns: '1fr 1fr' }}>
|
||||
<Button onClick={() => props.save()}>Save Game</Button>
|
||||
<Button onClick={() => setDeleteOpen(true)}>Delete Game</Button>
|
||||
</Box>
|
||||
<Box>
|
||||
<Box sx={{ display: 'grid', gridTemplateColumns: '1fr 1fr' }}>
|
||||
<Tooltip title={<Typography>Export your game to a text file.</Typography>}>
|
||||
<Button onClick={() => props.export()}>
|
||||
<DownloadIcon color="primary" />
|
||||
Export Game
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Tooltip title={<Typography>Import your game from a text file.<br/>This will <strong>overwrite</strong> your current game. Back it up first!</Typography>}>
|
||||
<Tooltip title={<Typography>Import your game from a text file.<br />This will <strong>overwrite</strong> your current game. Back it up first!</Typography>}>
|
||||
<Button onClick={startImport}>
|
||||
<UploadIcon color="primary" />
|
||||
Import Game
|
||||
@ -728,7 +571,7 @@ export function GameOptionsRoot(props: IProps): React.ReactElement {
|
||||
}
|
||||
/>
|
||||
</Box>
|
||||
<Box>
|
||||
<Box sx={{ display: 'grid' }}>
|
||||
<Tooltip
|
||||
title={
|
||||
<Typography>
|
||||
@ -743,7 +586,7 @@ export function GameOptionsRoot(props: IProps): React.ReactElement {
|
||||
<Button onClick={() => props.forceKill()}>Force kill all active scripts</Button>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
<Box>
|
||||
<Box sx={{ display: 'grid', gridTemplateColumns: '1fr 1fr' }}>
|
||||
<Tooltip
|
||||
title={
|
||||
<Typography>
|
||||
@ -770,7 +613,7 @@ export function GameOptionsRoot(props: IProps): React.ReactElement {
|
||||
<Button onClick={() => setDiagnosticOpen(true)}>Diagnose files</Button>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
<Box>
|
||||
<Box sx={{ display: 'grid', gridTemplateColumns: '1fr 1fr' }}>
|
||||
<Button onClick={() => setThemeEditorOpen(true)}>Theme editor</Button>
|
||||
<Button onClick={() => setStyleEditorOpen(true)}>Style editor</Button>
|
||||
</Box>
|
||||
@ -794,7 +637,7 @@ export function GameOptionsRoot(props: IProps): React.ReactElement {
|
||||
<Typography>Incremental game plaza</Typography>
|
||||
</Link>
|
||||
</Box>
|
||||
</Grid>
|
||||
</Box>
|
||||
</Grid>
|
||||
<FileDiagnosticModal open={diagnosticOpen} onClose={() => setDiagnosticOpen(false)} />
|
||||
<ConfirmationModal
|
||||
|
@ -39,12 +39,14 @@ export function LogBoxManager(): React.ReactElement {
|
||||
() =>
|
||||
LogBoxEvents.subscribe((script: RunningScript) => {
|
||||
const id = script.server + "-" + script.filename + script.args.map((x: any): string => `${x}`).join("-");
|
||||
if (logs.find((l) => l.id === id)) return;
|
||||
logs.push({
|
||||
id: id,
|
||||
script: script,
|
||||
});
|
||||
rerender();
|
||||
if (logs.find((l) => l.id === id)) close(id);
|
||||
Promise.resolve().then(() => {
|
||||
logs.push({
|
||||
id: id,
|
||||
script: script,
|
||||
});
|
||||
rerender();
|
||||
})
|
||||
}),
|
||||
[],
|
||||
);
|
||||
|
30
src/ui/React/OptionSwitch.tsx
Normal file
30
src/ui/React/OptionSwitch.tsx
Normal file
@ -0,0 +1,30 @@
|
||||
import { FormControlLabel, Switch, Tooltip, Typography } from "@mui/material";
|
||||
import React, { useEffect, useState } from "react";
|
||||
|
||||
interface IProps {
|
||||
checked: boolean;
|
||||
onChange: (newValue: boolean, error?: string) => void;
|
||||
text: React.ReactNode;
|
||||
tooltip: React.ReactNode;
|
||||
}
|
||||
|
||||
export function OptionSwitch({ checked, onChange, text, tooltip }: IProps): React.ReactElement {
|
||||
const [value, setValue] = useState(checked);
|
||||
|
||||
function handleSwitchChange(event: React.ChangeEvent<HTMLInputElement>): void {
|
||||
setValue(event.target.checked);
|
||||
}
|
||||
|
||||
useEffect(() => onChange(value), [value]);
|
||||
|
||||
return (
|
||||
<FormControlLabel
|
||||
control={<Switch checked={value} onChange={handleSwitchChange} />}
|
||||
label={
|
||||
<Tooltip title={<Typography>{tooltip}</Typography>}>
|
||||
<Typography>{text}</Typography>
|
||||
</Tooltip>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
@ -1,21 +1,19 @@
|
||||
import React, { useState } from "react";
|
||||
|
||||
import React, { useState, useEffect, useRef } from "react";
|
||||
import Draggable, { DraggableEventHandler } from "react-draggable";
|
||||
import makeStyles from "@mui/styles/makeStyles";
|
||||
import Collapse from "@mui/material/Collapse";
|
||||
import Fab from "@mui/material/Fab";
|
||||
import Paper from "@mui/material/Paper";
|
||||
import VisibilityOffIcon from "@mui/icons-material/VisibilityOff";
|
||||
import VisibilityIcon from "@mui/icons-material/Visibility";
|
||||
import KeyboardArrowDownIcon from "@mui/icons-material/KeyboardArrowDown";
|
||||
import KeyboardArrowUpIcon from "@mui/icons-material/KeyboardArrowUp";
|
||||
import EqualizerIcon from "@mui/icons-material/Equalizer";
|
||||
import SchoolIcon from "@mui/icons-material/School";
|
||||
import { use } from "../Context";
|
||||
import { Page } from "../Router";
|
||||
import { Settings } from "../../Settings/Settings";
|
||||
import { Box, Button, Typography } from "@mui/material";
|
||||
import { debounce } from "lodash";
|
||||
|
||||
const useStyles = makeStyles({
|
||||
visibilityToggle: {
|
||||
backgroundColor: "transparent",
|
||||
position: "absolute",
|
||||
top: "100%",
|
||||
right: 0,
|
||||
},
|
||||
overviewContainer: {
|
||||
position: "fixed",
|
||||
top: 0,
|
||||
@ -25,31 +23,116 @@ const useStyles = makeStyles({
|
||||
justifyContent: "flex-end",
|
||||
flexDirection: "column",
|
||||
},
|
||||
|
||||
header: {
|
||||
cursor: "grab",
|
||||
textAlign: "center",
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
},
|
||||
|
||||
visibilityToggle: {
|
||||
padding: "2px",
|
||||
minWidth: "inherit",
|
||||
backgroundColor: "transparent",
|
||||
border: "none",
|
||||
"&:hover": {
|
||||
backgroundColor: "rgba(255, 255, 255, 0.1)",
|
||||
},
|
||||
},
|
||||
|
||||
collapse: {
|
||||
borderTop: `1px solid ${Settings.theme.welllight}`,
|
||||
margin: "0 auto",
|
||||
},
|
||||
|
||||
icon: {
|
||||
fontSize: "24px",
|
||||
},
|
||||
});
|
||||
|
||||
interface IProps {
|
||||
children: JSX.Element[] | JSX.Element | React.ReactElement[] | React.ReactElement;
|
||||
mode: "tutorial" | "overview";
|
||||
}
|
||||
|
||||
export function Overview({ children }: IProps): React.ReactElement {
|
||||
const [open, setOpen] = useState(true);
|
||||
export interface OverviewSettings {
|
||||
opened: boolean;
|
||||
x: number;
|
||||
y: number;
|
||||
}
|
||||
|
||||
export function Overview({ children, mode }: IProps): React.ReactElement {
|
||||
const draggableRef = useRef<HTMLDivElement>(null);
|
||||
const [open, setOpen] = useState(Settings.overview.opened);
|
||||
const [x, setX] = useState(Settings.overview.x);
|
||||
const [y, setY] = useState(Settings.overview.y);
|
||||
const classes = useStyles();
|
||||
const router = use.Router();
|
||||
|
||||
const CurrentIcon = open ? KeyboardArrowUpIcon : KeyboardArrowDownIcon;
|
||||
const LeftIcon = mode === "tutorial" ? SchoolIcon : EqualizerIcon;
|
||||
const header = mode === "tutorial" ? "Tutorial" : "Overview";
|
||||
const handleStop: DraggableEventHandler = (e, data) => {
|
||||
setX(data.x);
|
||||
setY(data.y);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
Settings.overview = { x, y, opened: open };
|
||||
}, [open, x, y]);
|
||||
|
||||
// Trigger fakeDrag once to make sure loaded data is not outside bounds
|
||||
useEffect(() => fakeDrag(), []);
|
||||
|
||||
// And trigger fakeDrag when the window is resized
|
||||
useEffect(() => {
|
||||
window.addEventListener("resize", fakeDrag);
|
||||
return () => {
|
||||
window.removeEventListener("resize", fakeDrag);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const fakeDrag = debounce((): void => {
|
||||
const node = draggableRef?.current;
|
||||
if (!node) return;
|
||||
|
||||
// No official way to trigger an onChange to recompute the bounds
|
||||
// See: https://github.com/react-grid-layout/react-draggable/issues/363#issuecomment-947751127
|
||||
triggerMouseEvent(node, "mouseover");
|
||||
triggerMouseEvent(node, "mousedown");
|
||||
triggerMouseEvent(document, "mousemove");
|
||||
triggerMouseEvent(node, "mouseup");
|
||||
triggerMouseEvent(node, "click");
|
||||
}, 100);
|
||||
|
||||
const triggerMouseEvent = (node: HTMLDivElement | Document, eventType: string): void => {
|
||||
const clickEvent = document.createEvent("MouseEvents");
|
||||
clickEvent.initEvent(eventType, true, true);
|
||||
node.dispatchEvent(clickEvent);
|
||||
};
|
||||
|
||||
if (router.page() === Page.BitVerse || router.page() === Page.Loading || router.page() === Page.Recovery)
|
||||
return <></>;
|
||||
let icon;
|
||||
if (open) {
|
||||
icon = <VisibilityOffIcon color="primary" />;
|
||||
} else {
|
||||
icon = <VisibilityIcon color="primary" />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Paper square classes={{ root: classes.overviewContainer }}>
|
||||
<Collapse in={open}>{children}</Collapse>
|
||||
<Fab size="small" classes={{ root: classes.visibilityToggle }} onClick={() => setOpen((old) => !old)}>
|
||||
{icon}
|
||||
</Fab>
|
||||
</Paper>
|
||||
<Draggable handle=".drag" bounds="body" onStop={handleStop} defaultPosition={{ x, y }}>
|
||||
<Paper className={classes.overviewContainer} square>
|
||||
<Box className="drag" onDoubleClick={() => setOpen((old) => !old)} ref={draggableRef}>
|
||||
<Box className={classes.header}>
|
||||
<LeftIcon color="secondary" className={classes.icon} sx={{ padding: "2px" }} />
|
||||
<Typography flexGrow={1} color="secondary">
|
||||
{header}
|
||||
</Typography>
|
||||
<Button variant="text" size="small" className={classes.visibilityToggle}>
|
||||
{<CurrentIcon className={classes.icon} color="secondary" onClick={() => setOpen((old) => !old)} />}
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
<Collapse in={open} className={classes.collapse}>
|
||||
{children}
|
||||
</Collapse>
|
||||
</Paper>
|
||||
</Draggable>
|
||||
);
|
||||
}
|
||||
|
@ -7,12 +7,13 @@ import Typography from "@mui/material/Typography";
|
||||
import Paper from "@mui/material/Paper";
|
||||
import TextField from "@mui/material/TextField";
|
||||
import ReplyIcon from "@mui/icons-material/Reply";
|
||||
import SaveIcon from '@mui/icons-material/Save';
|
||||
import SaveIcon from "@mui/icons-material/Save";
|
||||
|
||||
import { ThemeEvents } from "./Theme";
|
||||
import { Settings } from "../../Settings/Settings";
|
||||
import { IStyleSettings, defaultStyles } from "../../Settings/Styles";
|
||||
import { defaultStyles } from "../../Settings/Styles";
|
||||
import { Tooltip } from "@mui/material";
|
||||
import { IStyleSettings } from "../../ScriptEditor/NetscriptDefinitions";
|
||||
|
||||
interface IProps {
|
||||
open: boolean;
|
||||
@ -25,16 +26,16 @@ interface FontFamilyProps {
|
||||
refreshId: number;
|
||||
}
|
||||
|
||||
function FontFamilyField({ value, onChange, refreshId } : FontFamilyProps): React.ReactElement {
|
||||
function FontFamilyField({ value, onChange, refreshId }: FontFamilyProps): React.ReactElement {
|
||||
const [errorText, setErrorText] = useState<string | undefined>();
|
||||
const [fontFamily, setFontFamily] = useState<React.CSSProperties["fontFamily"]>(value);
|
||||
|
||||
function update(newValue: React.CSSProperties["fontFamily"]): void {
|
||||
setFontFamily(newValue);
|
||||
if (!newValue) {
|
||||
setErrorText('Must have a value');
|
||||
setErrorText("Must have a value");
|
||||
} else {
|
||||
setErrorText('');
|
||||
setErrorText("");
|
||||
}
|
||||
}
|
||||
|
||||
@ -55,7 +56,7 @@ function FontFamilyField({ value, onChange, refreshId } : FontFamilyProps): Reac
|
||||
onChange={onTextChange}
|
||||
fullWidth
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
interface LineHeightProps {
|
||||
@ -64,18 +65,18 @@ interface LineHeightProps {
|
||||
refreshId: number;
|
||||
}
|
||||
|
||||
function LineHeightField({ value, onChange, refreshId } : LineHeightProps): React.ReactElement {
|
||||
function LineHeightField({ value, onChange, refreshId }: LineHeightProps): React.ReactElement {
|
||||
const [errorText, setErrorText] = useState<string | undefined>();
|
||||
const [lineHeight, setLineHeight] = useState<React.CSSProperties["lineHeight"]>(value);
|
||||
|
||||
function update(newValue: React.CSSProperties["lineHeight"]): void {
|
||||
setLineHeight(newValue);
|
||||
if (!newValue) {
|
||||
setErrorText('Must have a value');
|
||||
setErrorText("Must have a value");
|
||||
} else if (isNaN(Number(newValue))) {
|
||||
setErrorText('Must be a number');
|
||||
setErrorText("Must be a number");
|
||||
} else {
|
||||
setErrorText('');
|
||||
setErrorText("");
|
||||
}
|
||||
}
|
||||
|
||||
@ -95,7 +96,7 @@ function LineHeightField({ value, onChange, refreshId } : LineHeightProps): Reac
|
||||
helperText={errorText}
|
||||
onChange={onTextChange}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export function StyleEditorModal(props: IProps): React.ReactElement {
|
||||
@ -115,7 +116,7 @@ export function StyleEditorModal(props: IProps): React.ReactElement {
|
||||
}
|
||||
|
||||
function setDefaults(): void {
|
||||
const styles = {...defaultStyles}
|
||||
const styles = { ...defaultStyles };
|
||||
setCustomStyle(styles);
|
||||
persistToSettings(styles);
|
||||
setRefreshId(refreshId + 1);
|
||||
@ -132,16 +133,23 @@ export function StyleEditorModal(props: IProps): React.ReactElement {
|
||||
<Modal open={props.open} onClose={props.onClose}>
|
||||
<Typography variant="h6">Styles Editor</Typography>
|
||||
<Typography>
|
||||
WARNING: Changing styles <strong>may mess up</strong> the interface. Drastic changes are <strong>NOT recommended</strong>.
|
||||
WARNING: Changing styles <strong>may mess up</strong> the interface. Drastic changes are{" "}
|
||||
<strong>NOT recommended</strong>.
|
||||
</Typography>
|
||||
<Paper sx={{ p: 2, my: 2 }}>
|
||||
<FontFamilyField value={customStyle.fontFamily} refreshId={refreshId}
|
||||
onChange={(value, error) => update({ ...customStyle, fontFamily: value }, error)} />
|
||||
<FontFamilyField
|
||||
value={customStyle.fontFamily}
|
||||
refreshId={refreshId}
|
||||
onChange={(value, error) => update({ ...customStyle, fontFamily: value as any }, error)}
|
||||
/>
|
||||
<br />
|
||||
<LineHeightField value={customStyle.lineHeight} refreshId={refreshId}
|
||||
onChange={(value, error) => update({ ...customStyle, lineHeight: value }, error)} />
|
||||
<LineHeightField
|
||||
value={customStyle.lineHeight}
|
||||
refreshId={refreshId}
|
||||
onChange={(value, error) => update({ ...customStyle, lineHeight: value as any }, error)}
|
||||
/>
|
||||
<br />
|
||||
<ButtonGroup sx={{ my: 1}}>
|
||||
<ButtonGroup sx={{ my: 1 }}>
|
||||
<Button onClick={setDefaults} startIcon={<ReplyIcon />} color="secondary" variant="outlined">
|
||||
Revert to Defaults
|
||||
</Button>
|
||||
|
29
test/Script/Script.test.ts
Normal file
29
test/Script/Script.test.ts
Normal file
@ -0,0 +1,29 @@
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
import { jest, describe, expect, test } from "@jest/globals";
|
||||
|
||||
import { Script } from "../../src/Script/Script";
|
||||
import { Player } from "../../src/Player";
|
||||
|
||||
jest.mock(`!!raw-loader!../NetscriptDefinitions.d.ts`, () => "", {
|
||||
virtual: true,
|
||||
});
|
||||
|
||||
const code = `/** @param {NS} ns **/
|
||||
export async function main(ns) {
|
||||
ns.print(ns.getWeakenTime('n00dles'));
|
||||
}`;
|
||||
|
||||
describe("Validate Save Script Works", function () {
|
||||
|
||||
it("Save", function () {
|
||||
const server = "home";
|
||||
const filename = "test.js";
|
||||
const player = Player;
|
||||
const script = new Script();
|
||||
script.saveScript(player, filename, code, server, []);
|
||||
|
||||
expect(script.filename).toEqual(filename)
|
||||
expect(script.code).toEqual(code)
|
||||
expect(script.server).toEqual(server)
|
||||
});
|
||||
});
|
Reference in New Issue
Block a user