mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2024-11-29 19:13:49 +01:00
Merge branch 'dev' into feature-catch-errors-and-softreset-recovery
This commit is contained in:
commit
a454e21977
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)
|
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
|
||||
|
36
dist/vendor.bundle.js
vendored
36
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
|
||||
|
@ -113,7 +113,7 @@ The list contains the name of (i.e. the value returned by
|
||||
| | | to any position from i to i+n. |
|
||||
| | | |
|
||||
| | | Assuming you are initially positioned at the start of the array, determine |
|
||||
| | | whether you are able to reach the last index of the array EXACTLY. |
|
||||
| | | whether you are able to reach the last index of the array. |
|
||||
+------------------------------------+------------------------------------------------------------------------------------------+
|
||||
| Merge Overlapping Intervals | | Given an array of intervals, merge all overlapping intervals. An interval |
|
||||
| | | is an array with two numbers, where the first number is always less than |
|
||||
|
@ -17,9 +17,16 @@ says 'Infiltrate Company'.
|
||||
When infiltrating a company you will be presented with short active challenges.
|
||||
None of the challenges use the mouse.
|
||||
|
||||
The difficulty at the top lowers with better combat stats. It is not recommended
|
||||
The difficulty at the top lowers with better combat stats and charisma. It is not recommended
|
||||
to attempt infiltrations above mid-normal.
|
||||
|
||||
The "maximum level" is the number of challenges you will need to pass to receive
|
||||
the infiltration reward.
|
||||
|
||||
Every time you fail an infiltration challenge, you will take damage based on the
|
||||
difficulty of the infiltration. If you are reduced to 0 hp or below, the
|
||||
infiltration will immediately end.
|
||||
|
||||
* Most use spacebar as "action"
|
||||
* Some use WASD or arrows interchangeably.
|
||||
* A few others use the rest of the keyboard.
|
||||
|
@ -16,7 +16,6 @@ Affects:
|
||||
* Chance to successfully hack a server
|
||||
* Percent money stolen when hacking a server
|
||||
* Success rate of certain crimes
|
||||
* Success rate of Hacking option during Infiltration
|
||||
* Time it takes to create a program
|
||||
* Faction reputation gain when carrying out Hacking Contracts or Field Work
|
||||
* Company reputation gain for certain jobs
|
||||
@ -26,7 +25,6 @@ Gain experience by:
|
||||
* Manually hacking servers through Terminal
|
||||
* Executing hack(), grow(), or weaken() through a script
|
||||
* Committing certain crimes
|
||||
* Infiltration
|
||||
* Carrying out Hacking Contracts or doing Field work for Factions
|
||||
* Working certain jobs at a company
|
||||
* Studying at a university
|
||||
@ -38,14 +36,12 @@ Represents the player's physical offensive power
|
||||
Affects:
|
||||
|
||||
* Success rate of certain crimes
|
||||
* Success rate of Combat options during Infiltration
|
||||
* Faction reputation gain for Security and Field Work
|
||||
* Company reputation gain for certain jobs
|
||||
|
||||
Gain experience by:
|
||||
|
||||
* Committing certain crimes
|
||||
* Infiltration
|
||||
* Working out at a gym
|
||||
* Doing Security/Field Work for a faction
|
||||
* Working certain jobs at a company
|
||||
@ -58,15 +54,12 @@ Affects:
|
||||
|
||||
* Success rate of certain crimes
|
||||
* The player's HP
|
||||
* Success rate of Combat options during Infiltration
|
||||
* How much damage the player takes during Infiltration
|
||||
* Faction reputation gain for Security and Field Work
|
||||
* Company reputation gain for certain jobs
|
||||
|
||||
Gain experience by:
|
||||
|
||||
* Committing certain crimes
|
||||
* Infiltration
|
||||
* Working out at a gym
|
||||
* Doing Security/Field Work for a faction
|
||||
* Working certain jobs at a company
|
||||
@ -78,14 +71,12 @@ Represents the player's skill and adeptness in performing certain tasks
|
||||
Affects:
|
||||
|
||||
* Success rate of certain crimes
|
||||
* Success rate of Combat, Lockpick, and Escape options during Infiltration
|
||||
* Faction reputation gain for Security and Field Work
|
||||
* Company reputation gain for certain jobs
|
||||
|
||||
Gain experience by:
|
||||
|
||||
* Committing certain crimes
|
||||
* Infiltration
|
||||
* Working out at a gym
|
||||
* Doing Security/Field Work for a faction
|
||||
* Working certain jobs at a company
|
||||
@ -97,14 +88,12 @@ Represents the player's speed and ability to move
|
||||
Affects:
|
||||
|
||||
* Success rate of certain crimes
|
||||
* Success rate of Combat, Sneak, and Escape options during Infiltration
|
||||
* Faction reputation gain for Security and Field Work
|
||||
* Company reputation gain for certain jobs
|
||||
|
||||
Gain experience by:
|
||||
|
||||
* Committing certain crimes
|
||||
* Infiltration
|
||||
* Working out at a gym
|
||||
* Doing Security/Field Work for a faction
|
||||
* Working certain jobs at a company
|
||||
@ -116,14 +105,12 @@ Represents the player's social abilities
|
||||
Affects:
|
||||
|
||||
* Success rate of certain crimes
|
||||
* Success rate of Bribe option during Infiltration
|
||||
* Faction reputation gain for Field Work
|
||||
* Company reputation gain for most jobs
|
||||
|
||||
Gain experience by:
|
||||
|
||||
* Committing certain crimes
|
||||
* Infiltration
|
||||
* Studying at a university
|
||||
* Working a relevant job at a company
|
||||
* Doing Field work for a Faction
|
||||
|
@ -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|
|
||||
@ -807,8 +806,7 @@ startup script. Feel free to adjust it to your liking.
|
||||
|
||||
// Array of all servers that don't need any ports opened
|
||||
// to gain root access. These have 16 GB of RAM
|
||||
var servers0Port = ["n00dles",
|
||||
"sigma-cosmetics",
|
||||
var servers0Port = ["sigma-cosmetics",
|
||||
"joesguns",
|
||||
"nectar-net",
|
||||
"hong-fang-tea",
|
||||
|
@ -66,8 +66,7 @@ And the data in port 1 will look like::
|
||||
WARNING: Port Handles only work in :ref:`netscriptjs`. They do not work in :ref:`netscript1`
|
||||
|
||||
The :js:func:`getPortHandle` Netscript function can be used to get a handle to a Netscript Port.
|
||||
This handle allows you to access several new port-related functions and the
|
||||
port's underlying data structure, which is just a JavaScript array. The functions are:
|
||||
This handle allows you to access several new port-related functions. The functions are:
|
||||
|
||||
.. js:method:: NetscriptPort.writePort(data)
|
||||
|
||||
@ -111,22 +110,11 @@ port's underlying data structure, which is just a JavaScript array. The function
|
||||
|
||||
Clears all data from the port. Works the same as the Netscript function `clear`
|
||||
|
||||
.. js:attribute:: NetscriptPort.data
|
||||
|
||||
The Netscript port underlying data structure, which is just a Javascript array. All
|
||||
valid Javascript Array methods can be called on this.
|
||||
|
||||
Port Handle Example::
|
||||
|
||||
port = getPortHandle(5);
|
||||
back = port.data.pop(); //Get and remove last element in port
|
||||
|
||||
//Remove an element from the port
|
||||
i = port.data.findIndex("foo");
|
||||
if (i != -1) {
|
||||
port.data.slice(i, 1);
|
||||
}
|
||||
|
||||
//Wait for port data before reading
|
||||
while(port.empty()) {
|
||||
sleep(10000);
|
||||
|
@ -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();
|
||||
|
@ -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']
|
||||
});
|
||||
}
|
||||
});
|
||||
|
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": {
|
||||
@ -484,3 +484,4 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -13,7 +13,6 @@ import { SourceFileFlags } from "../SourceFile/SourceFileFlags";
|
||||
|
||||
import { dialogBoxCreate } from "../ui/React/DialogBox";
|
||||
import { clearObject } from "../utils/helpers/clearObject";
|
||||
import { Router } from "../ui/GameRoot";
|
||||
|
||||
import { WHRNG } from "../Casino/RNG";
|
||||
|
||||
@ -2583,7 +2582,6 @@ function installAugmentations(): boolean {
|
||||
augmentationList += aug.name + level + "<br>";
|
||||
}
|
||||
Player.queuedAugmentations = [];
|
||||
|
||||
dialogBoxCreate(
|
||||
"You slowly drift to sleep as scientists put you under in order " +
|
||||
"to install the following Augmentations:<br>" +
|
||||
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { CompanyPosition } from "./CompanyPosition";
|
||||
import * as posNames from "./data/companypositionnames";
|
||||
import { favorToRep, repToFavor } from "../Faction/formulas/favor";
|
||||
|
||||
import { CONSTANTS } from "../Constants";
|
||||
import { IMap } from "../types";
|
||||
|
||||
import { Generic_fromJSON, Generic_toJSON, Reviver } from "../utils/JSONReviver";
|
||||
@ -71,7 +71,6 @@ export class Company {
|
||||
isPlayerEmployed: boolean;
|
||||
playerReputation: number;
|
||||
favor: number;
|
||||
rolloverRep: number;
|
||||
|
||||
constructor(p: IConstructorParams = DefaultConstructorParams) {
|
||||
this.name = p.name;
|
||||
@ -84,7 +83,6 @@ export class Company {
|
||||
this.isPlayerEmployed = false;
|
||||
this.playerReputation = 1;
|
||||
this.favor = 0;
|
||||
this.rolloverRep = 0;
|
||||
this.isMegacorp = false;
|
||||
if (p.isMegacorp) this.isMegacorp = true;
|
||||
}
|
||||
@ -137,39 +135,17 @@ export class Company {
|
||||
if (this.favor == null) {
|
||||
this.favor = 0;
|
||||
}
|
||||
if (this.rolloverRep == null) {
|
||||
this.rolloverRep = 0;
|
||||
}
|
||||
const res = this.getFavorGain();
|
||||
if (res.length != 2) {
|
||||
console.error("Invalid result from getFavorGain() function");
|
||||
return;
|
||||
}
|
||||
|
||||
this.favor += res[0];
|
||||
this.rolloverRep = res[1];
|
||||
this.favor += this.getFavorGain();
|
||||
}
|
||||
|
||||
getFavorGain(): number[] {
|
||||
getFavorGain(): number {
|
||||
if (this.favor == null) {
|
||||
this.favor = 0;
|
||||
}
|
||||
if (this.rolloverRep == null) {
|
||||
this.rolloverRep = 0;
|
||||
}
|
||||
let favorGain = 0,
|
||||
rep = this.playerReputation + this.rolloverRep;
|
||||
let reqdRep = CONSTANTS.CompanyReputationToFavorBase * Math.pow(CONSTANTS.CompanyReputationToFavorMult, this.favor);
|
||||
while (rep > 0) {
|
||||
if (rep >= reqdRep) {
|
||||
++favorGain;
|
||||
rep -= reqdRep;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
reqdRep *= CONSTANTS.FactionReputationToFavorMult;
|
||||
}
|
||||
return [favorGain, rep];
|
||||
const storedRep = Math.max(0, favorToRep(this.favor));
|
||||
const totalRep = storedRep + this.playerReputation;
|
||||
const newFavor = repToFavor(totalRep);
|
||||
return newFavor - this.favor;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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>
|
||||
|
@ -18,8 +18,18 @@ import { HacknetNodeConstants } from "./data/Constants";
|
||||
|
||||
import { dialogBoxCreate } from "../ui/React/DialogBox";
|
||||
import { Generic_fromJSON, Generic_toJSON, Reviver } from "../utils/JSONReviver";
|
||||
import { ObjectValidator, minMax } from "../utils/Validator";
|
||||
|
||||
export class HacknetNode implements IHacknetNode {
|
||||
|
||||
static validationData: ObjectValidator<HacknetNode> = {
|
||||
cores: minMax(1, 1, HacknetNodeConstants.MaxCores),
|
||||
level: minMax(1, 1, HacknetNodeConstants.MaxLevel),
|
||||
ram: minMax(1, 1, HacknetNodeConstants.MaxRam),
|
||||
onlineTimeSeconds: minMax(0, 0, Infinity),
|
||||
totalMoneyGenerated: minMax(0, 0, Infinity)
|
||||
}
|
||||
|
||||
// Node's number of cores
|
||||
cores = 1;
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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 && (
|
||||
<>
|
||||
|
@ -198,7 +198,7 @@ export function CompanyLocation(props: IProps): React.ReactElement {
|
||||
<Tooltip
|
||||
title={
|
||||
<>
|
||||
You will have <Favor favor={company.favor + favorGain[0]} /> company favor upon resetting after
|
||||
You will have <Favor favor={company.favor + favorGain} /> company favor upon resetting after
|
||||
installing Augmentations
|
||||
</>
|
||||
}
|
||||
@ -226,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;
|
||||
@ -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`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -161,8 +161,6 @@ export function prestigeAugmentation(): void {
|
||||
resetPidCounter();
|
||||
ProgramsSeen.splice(0, ProgramsSeen.length);
|
||||
InvitationsSeen.splice(0, InvitationsSeen.length);
|
||||
|
||||
Router.clearHistory();
|
||||
}
|
||||
|
||||
// Prestige by destroying Bit Node and gaining a Source File
|
||||
@ -308,7 +306,5 @@ export function prestigeSourceFile(flume: boolean): void {
|
||||
// Gain int exp
|
||||
if (SourceFileFlags[5] !== 0 && !flume) Player.gainIntelligenceExp(300);
|
||||
|
||||
Router.clearHistory();
|
||||
|
||||
resetPidCounter();
|
||||
}
|
||||
|
@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -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;
|
||||
|
230
src/ScriptEditor/NetscriptDefinitions.d.ts
vendored
230
src/ScriptEditor/NetscriptDefinitions.d.ts
vendored
@ -109,32 +109,6 @@ 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
|
||||
@ -280,6 +254,24 @@ export interface AugmentPair {
|
||||
cost: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export enum PositionTypes {
|
||||
Long = "L",
|
||||
Short = "S",
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export enum OrderTypes {
|
||||
LimitBuy = "Limit Buy Order",
|
||||
LimitSell = "Limit Sell Order",
|
||||
StopBuy = "Stop Buy Order",
|
||||
StopSell = "Stop Sell Order",
|
||||
}
|
||||
|
||||
/**
|
||||
* Value in map of {@link StockOrder}
|
||||
* @public
|
||||
@ -290,17 +282,18 @@ export interface StockOrderObject {
|
||||
/** Price per share */
|
||||
price: number;
|
||||
/** Order type */
|
||||
type: string;
|
||||
type: OrderTypes;
|
||||
/** Order position */
|
||||
position: string;
|
||||
position: PositionTypes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return value of {@link TIX.getOrders | getOrders}
|
||||
*
|
||||
* Keys are stock symbols, properties are arrays of {@link StockOrderObject}
|
||||
* @public
|
||||
*/
|
||||
export interface StockOrder {
|
||||
/** Stock Symbol */
|
||||
[key: string]: StockOrderObject[];
|
||||
}
|
||||
|
||||
@ -488,6 +481,8 @@ export interface BitNodeMultipliers {
|
||||
FourSigmaMarketDataApiCost: number;
|
||||
/** Influences how much it costs to unlock the stock market's 4S Market Data (NOT API) */
|
||||
FourSigmaMarketDataCost: number;
|
||||
/** Influences the respect gain and money gain of your gang. */
|
||||
GangSoftcap: number;
|
||||
/** Influences the experienced gained when hacking a server. */
|
||||
HackExpGain: number;
|
||||
/** Influences how quickly the player's hacking level (not experience) scales */
|
||||
@ -508,10 +503,14 @@ export interface BitNodeMultipliers {
|
||||
PurchasedServerLimit: number;
|
||||
/** Influences the maximum allowed RAM for a purchased server */
|
||||
PurchasedServerMaxRam: number;
|
||||
/** Influences cost of any purchased server at or above 128GB */
|
||||
PurchasedServerSoftCap: number;
|
||||
/** Influences the minimum favor the player must have with a faction before they can donate to gain rep. */
|
||||
RepToDonateToFaction: number;
|
||||
/** Influences how much money can be stolen from a server when a script performs a hack against it. */
|
||||
/** Influences how much the money on a server can be reduced when a script performs a hack against it. */
|
||||
ScriptHackMoney: number;
|
||||
/** Influences how much of the money stolen by a scripted hack will be added to the player's money. */
|
||||
ScriptHackMoneyGain: number;
|
||||
/** Influences the growth percentage per cycle against a server. */
|
||||
ServerGrowthRate: number;
|
||||
/** Influences the maxmimum money that a server can grow to. */
|
||||
@ -524,6 +523,12 @@ export interface BitNodeMultipliers {
|
||||
ServerWeakenRate: number;
|
||||
/** Influences how quickly the player's strength level (not exp) scales */
|
||||
StrengthLevelMultiplier: number;
|
||||
/** Influences the power of the gift */
|
||||
StaneksGiftPowerMultiplier: number;
|
||||
/** Influences the size of the gift */
|
||||
StaneksGiftExtraSize: number;
|
||||
/** Influences the hacking skill required to backdoor the world daemon. */
|
||||
WorldDaemonDifficulty: number;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -535,9 +540,9 @@ export interface NodeStats {
|
||||
name: string;
|
||||
/** Node's level */
|
||||
level: number;
|
||||
/** Node's RAM */
|
||||
/** Node's RAM (GB) */
|
||||
ram: number;
|
||||
/** Node's used RAM */
|
||||
/** Node's used RAM (GB) */
|
||||
ramUsed: number;
|
||||
/** Node's number of cores */
|
||||
cores: number;
|
||||
@ -956,6 +961,78 @@ export interface SleeveTask {
|
||||
factionWorkType: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Object representing a port. A port is a serialized queue.
|
||||
* @public
|
||||
*/
|
||||
export interface NetscriptPort {
|
||||
/**
|
||||
* Write data to a port.
|
||||
* @remarks
|
||||
* RAM cost: 0 GB
|
||||
*
|
||||
* @returns The data popped off the queue if it was full.
|
||||
*/
|
||||
write(value: string|number): null|string|number;
|
||||
|
||||
/**
|
||||
* Attempt to write data to the port.
|
||||
* @remarks
|
||||
* RAM cost: 0 GB
|
||||
*
|
||||
* @returns True if the data was added to the port, false if the port was full
|
||||
*/
|
||||
tryWrite(value: string|number): boolean;
|
||||
|
||||
/**
|
||||
* Shift an element out of the port.
|
||||
* @remarks
|
||||
* RAM cost: 0 GB
|
||||
*
|
||||
* This function will remove the first element from the port and return it.
|
||||
* If the port is empty, then the string “NULL PORT DATA” will be returned.
|
||||
* @returns the data read.
|
||||
*/
|
||||
read(): string|number;
|
||||
|
||||
/**
|
||||
* Retrieve the first element from the port without removing it.
|
||||
* @remarks
|
||||
* RAM cost: 0 GB
|
||||
*
|
||||
* This function is used to peek at the data from a port. It returns the
|
||||
* first element in the specified port without removing that element. If
|
||||
* the port is empty, the string “NULL PORT DATA” will be returned.
|
||||
* @returns the data read
|
||||
*/
|
||||
peek(): string|number;
|
||||
|
||||
/**
|
||||
* Check if the port is full.
|
||||
* @remarks
|
||||
* RAM cost: 0 GB
|
||||
*
|
||||
* @returns true if the port is full, otherwise false
|
||||
*/
|
||||
full(): boolean;
|
||||
|
||||
/**
|
||||
* Check if the port is empty.
|
||||
* @remarks
|
||||
* RAM cost: 0 GB
|
||||
*
|
||||
* @returns true if the port is empty, otherwise false
|
||||
*/
|
||||
empty(): boolean;
|
||||
|
||||
/**
|
||||
* Empties all data from the port.
|
||||
* @remarks
|
||||
* RAM cost: 0 GB
|
||||
*/
|
||||
clear(): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stock market API
|
||||
* @public
|
||||
@ -1213,6 +1290,8 @@ export interface TIX {
|
||||
* @remarks
|
||||
* RAM cost: 2.5 GB
|
||||
* This is an object containing information for all the Limit and Stop Orders you have in the stock market.
|
||||
* For each symbol you have a position in, the returned object will have a key with that symbol's name.
|
||||
* The object's properties are each an array of {@link StockOrderObject}
|
||||
* The object has the following structure:
|
||||
*
|
||||
* ```ts
|
||||
@ -1320,7 +1399,7 @@ export interface TIX {
|
||||
/**
|
||||
* Singularity API
|
||||
* @remarks
|
||||
* This API requires Source-File 4 level 1 to use. The RAM cost of all these functions is multiplied by 16/4/1 based on Source-File 4 levels.
|
||||
* This API requires Source-File 4 to use. The RAM cost of all these functions is multiplied by 16/4/1 based on Source-File 4 levels.
|
||||
* @public
|
||||
*/
|
||||
export interface Singularity {
|
||||
@ -3880,6 +3959,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;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -4410,11 +4519,11 @@ export interface NS extends Singularity {
|
||||
* //Get logs from foo.script on the foodnstuff server that was run with the arguments [1, "test"]
|
||||
* ns.tail("foo.script", "foodnstuff", 1, "test");
|
||||
* ```
|
||||
* @param fn - Optional. Filename of the script being tailed. If omitted, the current script is tailed.
|
||||
* @param fn - Optional. Filename or PID of the script being tailed. If omitted, the current script is tailed.
|
||||
* @param host - Optional. Hostname of the script being tailed. Defaults to the server this script is running on. If args are specified, this is not optional.
|
||||
* @param args - Arguments for the script being tailed.
|
||||
*/
|
||||
tail(fn?: string, host?: string, ...args: any[]): void;
|
||||
tail(fn?: FilenameOrPID, host?: string, ...args: any[]): void;
|
||||
|
||||
/**
|
||||
* Get the list of servers connected to a server.
|
||||
@ -5064,7 +5173,7 @@ export interface NS extends Singularity {
|
||||
* const [totalRam, ramUsed] = ns.getServerRam("helios");
|
||||
* ```
|
||||
* @param host - Host of target server.
|
||||
* @returns Array with total and used memory on the specified server.
|
||||
* @returns Array with total and used memory on the specified server, in GB.
|
||||
*/
|
||||
getServerRam(host: string): [number, number];
|
||||
|
||||
@ -5074,7 +5183,7 @@ export interface NS extends Singularity {
|
||||
* RAM cost: 0.05 GB
|
||||
*
|
||||
* @param host - Hostname of the target server.
|
||||
* @returns max ram
|
||||
* @returns max ram (GB)
|
||||
*/
|
||||
getServerMaxRam(host: string): number;
|
||||
/**
|
||||
@ -5083,7 +5192,7 @@ export interface NS extends Singularity {
|
||||
* RAM cost: 0.05 GB
|
||||
*
|
||||
* @param host - Hostname of the target server.
|
||||
* @returns used ram
|
||||
* @returns used ram (GB)
|
||||
*/
|
||||
getServerUsedRam(host: string): number;
|
||||
|
||||
@ -5157,6 +5266,7 @@ export interface NS extends Singularity {
|
||||
* RAM cost: 0.1 GB
|
||||
*
|
||||
* Returns a boolean indicating whether the specified script is running on the target server.
|
||||
* If you use a PID instead of a filename, the hostname and args parameters are unnecessary.
|
||||
* Remember that a script is uniquely identified by both its name and its arguments.
|
||||
*
|
||||
* @example
|
||||
@ -5183,12 +5293,12 @@ export interface NS extends Singularity {
|
||||
* //The function call will return true if there is a script named foo.script running with the arguments 1, 5, and “test” (in that order) on the joesguns server, and false otherwise:
|
||||
* ns.isRunning("foo.script", "joesguns", 1, 5, "test");
|
||||
* ```
|
||||
* @param script - Filename of script to check. This is case-sensitive.
|
||||
* @param script - Filename or PID of script to check. This is case-sensitive.
|
||||
* @param host - Host of target server.
|
||||
* @param args - Arguments to specify/identify which scripts to search for.
|
||||
* @returns True if specified script is running on the target server, and false otherwise.
|
||||
*/
|
||||
isRunning(script: string, host: string, ...args: string[]): boolean;
|
||||
isRunning(script: FilenameOrPID, host: string, ...args: string[]): boolean;
|
||||
|
||||
/**
|
||||
* Get general info about a running script.
|
||||
@ -5196,10 +5306,14 @@ export interface NS extends Singularity {
|
||||
* RAM cost: 0.3 GB
|
||||
*
|
||||
* Running with no args returns curent script.
|
||||
* If you use a PID as the first parameter, the hostname and args parameters are unnecessary.
|
||||
*
|
||||
* @param filename - Optional. Filename or PID of the script.
|
||||
* @param hostname - Optional. Name of host server the script is running on.
|
||||
* @param args - Arguments to identify the script
|
||||
* @returns info about a running script
|
||||
*/
|
||||
getRunningScript(filename?: string | number, hostname?: string, ...args: (string | number)[]): RunningScript;
|
||||
getRunningScript(filename?: FilenameOrPID, hostname?: string, ...args: (string | number)[]): RunningScript;
|
||||
|
||||
/**
|
||||
* Get cost of purchasing a server.
|
||||
@ -5222,7 +5336,7 @@ export interface NS extends Singularity {
|
||||
* ns.tprint(i + " -- " + ns.getPurchasedServerCost(Math.pow(2, i)));
|
||||
* }
|
||||
* ```
|
||||
* @param ram - Amount of RAM of a potential purchased server. Must be a power of 2 (2, 4, 8, 16, etc.). Maximum value of 1048576 (2^20).
|
||||
* @param ram - Amount of RAM of a potential purchased server, in GB. Must be a power of 2 (2, 4, 8, 16, etc.). Maximum value of 1048576 (2^20).
|
||||
* @returns The cost to purchase a server with the specified amount of ram.
|
||||
*/
|
||||
getPurchasedServerCost(ram: number): number;
|
||||
@ -5270,7 +5384,7 @@ export interface NS extends Singularity {
|
||||
* }
|
||||
* ```
|
||||
* @param hostname - Host of the purchased server.
|
||||
* @param ram - Amount of RAM of the purchased server. Must be a power of 2 (2, 4, 8, 16, etc.). Maximum value of 1048576 (2^20).
|
||||
* @param ram - Amount of RAM of the purchased server, in GB. Must be a power of 2 (2, 4, 8, 16, etc.). Maximum value of 1048576 (2^20).
|
||||
* @returns The hostname of the newly purchased server.
|
||||
*/
|
||||
purchaseServer(hostname: string, ram: number): string;
|
||||
@ -5311,7 +5425,7 @@ export interface NS extends Singularity {
|
||||
* Returns the maximum RAM that a purchased server can have.
|
||||
*
|
||||
* @remarks RAM cost: 0.05 GB
|
||||
* @returns Returns the maximum RAM that a purchased server can have.
|
||||
* @returns Returns the maximum RAM (in GB) that a purchased server can have.
|
||||
*/
|
||||
getPurchasedServerMaxRam(): number;
|
||||
|
||||
@ -5320,7 +5434,7 @@ export interface NS extends Singularity {
|
||||
* @remarks
|
||||
* RAM cost: 0 GB
|
||||
*
|
||||
* This function can be used to either write data to a text file (.txt).
|
||||
* This function can be used to write data to a text file (.txt).
|
||||
*
|
||||
* This function will write data to that text file. If the specified text file does not exist,
|
||||
* then it will be created. The third argument mode, defines how the data will be written to
|
||||
@ -5329,7 +5443,7 @@ export interface NS extends Singularity {
|
||||
* then the data will be written in “append” mode which means that the data will be added at the
|
||||
* end of the text file.
|
||||
*
|
||||
* @param handle - Port or text file that will be written to.
|
||||
* @param handle - Filename of the text file that will be written to.
|
||||
* @param data - Data to write.
|
||||
* @param mode - Defines the write mode. Only valid when writing to text files.
|
||||
*/
|
||||
@ -5355,13 +5469,13 @@ export interface NS extends Singularity {
|
||||
* @remarks
|
||||
* RAM cost: 0 GB
|
||||
*
|
||||
* This function is used to read data from a port or from a text file (.txt).
|
||||
* This function is used to read data from a text file (.txt).
|
||||
*
|
||||
* This function will return the data in the specified text
|
||||
* file. If the text file does not exist, an empty string will be returned.
|
||||
*
|
||||
* @param handle - Port or text file to read from.
|
||||
* @returns Data in the specified text file or port.
|
||||
* @param handle - Filename to read from.
|
||||
* @returns Data in the specified text file.
|
||||
*/
|
||||
read(handle: string): any;
|
||||
|
||||
@ -5433,9 +5547,8 @@ export interface NS extends Singularity {
|
||||
*
|
||||
* @see https://bitburner.readthedocs.io/en/latest/netscript/netscriptmisc.html#netscript-ports
|
||||
* @param port - Port number. Must be an integer between 1 and 20.
|
||||
* @returns Data in the specified port.
|
||||
*/
|
||||
getPortHandle(port: number): IPort;
|
||||
getPortHandle(port: number): NetscriptPort;
|
||||
|
||||
/**
|
||||
* Delete a file.
|
||||
@ -5518,7 +5631,7 @@ export interface NS extends Singularity {
|
||||
*
|
||||
* @param script - Filename of script. This is case-sensitive.
|
||||
* @param host - Host of target server the script is located on. This is optional, If it is not specified then the function will se the current server as the target server.
|
||||
* @returns Amount of RAM required to run the specified script on the target server, and 0 if the script does not exist.
|
||||
* @returns Amount of RAM (in GB) required to run the specified script on the target server, and 0 if the script does not exist.
|
||||
*/
|
||||
getScriptRam(script: string, host?: string): number;
|
||||
|
||||
@ -5630,7 +5743,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.
|
||||
@ -5642,7 +5755,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
|
||||
@ -6354,3 +6467,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>
|
||||
|
@ -1,9 +1,10 @@
|
||||
import { ISelfInitializer, ISelfLoading } from "../types";
|
||||
import { OwnedAugmentationsOrderSetting, PurchaseAugmentationsOrderSetting } from "./SettingEnums";
|
||||
import { defaultTheme, ITheme } from "./Themes";
|
||||
import { defaultStyles, IStyleSettings } from "./Styles";
|
||||
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.
|
||||
|
@ -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,
|
||||
|
@ -29,7 +29,7 @@ export const TerminalHelpText: string[] = [
|
||||
"lscpu Displays the number of CPU cores on the machine",
|
||||
"mem [script] [-t n] Displays the amount of RAM required to run the script",
|
||||
"mv [src] [dest] Move/rename a text or script file",
|
||||
"nano [file ...] Text editor - Open up and edit one or more scripts or text files",
|
||||
"nano [file ...] | [glob] Text editor - Open up and edit one or more scripts or text files",
|
||||
"ps Display all scripts that are currently running",
|
||||
"rm [file] Delete a file from the server",
|
||||
"run [name] [-t n] [--tail] [args...] Execute a program or script",
|
||||
@ -40,36 +40,58 @@ export const TerminalHelpText: string[] = [
|
||||
"tail [script] [args...] Displays dynamic logs for the specified script",
|
||||
"top Displays all running scripts and their RAM usage",
|
||||
"unalias [alias name] Deletes the specified alias",
|
||||
"vim [file ...] Text editor - Open up and edit one or more scripts or text files in vim mode",
|
||||
"vim [file ...] | [glob] Text editor - Open up and edit one or more scripts or text files in vim mode",
|
||||
"weaken Reduce the security of the current machine",
|
||||
"wget [url] [target file] Retrieves code/text from a web server",
|
||||
];
|
||||
|
||||
const TemplatedHelpTexts: IMap<(command: string) => string[]> = {
|
||||
scriptEditor: (command) => {
|
||||
return [
|
||||
`${command} [file ...] | [glob]`,
|
||||
` `,
|
||||
`Opens up the specified file(s) in the Script Editor. Only scripts (.js, .ns, .script) or text files (.txt) `,
|
||||
`can be edited using the Script Editor. If a file does not exist a new one will be created`,
|
||||
` `,
|
||||
`If provided a glob as the only argument, ${command} can spider directories and open all matching `,
|
||||
`files at once. ${command} cannot create files using globs, so your scripts must already exist.`,
|
||||
` `,
|
||||
`Examples:`,
|
||||
` `,
|
||||
`${command} test.js`,
|
||||
`${command} test.js test2.js`,
|
||||
` `,
|
||||
`${command} test.*`,
|
||||
`${command} /my-dir/*.js`,
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
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. ",
|
||||
" ",
|
||||
@ -80,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.",
|
||||
" ",
|
||||
@ -96,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.",
|
||||
" ",
|
||||
@ -106,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).",
|
||||
" ",
|
||||
@ -178,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:",
|
||||
" ",
|
||||
@ -186,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.",
|
||||
" ",
|
||||
@ -235,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. ",
|
||||
@ -265,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 ",
|
||||
@ -302,28 +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",
|
||||
],
|
||||
nano: [
|
||||
"nano [file ...]",
|
||||
" mv myScript.js myOldScript.js",
|
||||
" ",
|
||||
"Opens up the specified file(s) in the Text Editor. Only scripts (.script) or text files (.txt) can be ",
|
||||
"edited using the Text Editor. If the file does not already exist, then a new, empty one ",
|
||||
"will be created",
|
||||
],
|
||||
ps: ["ps", " ", "Prints all scripts that are running on the current server"],
|
||||
|
||||
nano: TemplatedHelpTexts.scriptEditor('nano'),
|
||||
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.",
|
||||
" ",
|
||||
@ -338,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 ",
|
||||
@ -360,71 +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: [
|
||||
"vim [file ...]",
|
||||
" ",
|
||||
"Opens up the specified file(s) in the Text Editor in vim mode. Only scripts (.script) or text files (.txt) can be ",
|
||||
"edited using the Text Editor. If the file does not already exist, then a new, empty one ",
|
||||
"will be created",
|
||||
],
|
||||
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, ",
|
||||
@ -433,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",
|
||||
" ",
|
||||
],
|
||||
};
|
||||
|
@ -1,9 +1,12 @@
|
||||
import { ITerminal } from "../../ITerminal";
|
||||
import { removeLeadingSlash, removeTrailingSlash } from '../../DirectoryHelpers'
|
||||
import { IRouter, ScriptEditorRouteOptions } from "../../../ui/Router";
|
||||
import { IPlayer } from "../../../PersonObjects/IPlayer";
|
||||
import { BaseServer } from "../../../Server/BaseServer";
|
||||
import { isScriptFilename } from "../../../Script/isScriptFilename";
|
||||
import { CursorPositions } from "../../../ScriptEditor/CursorPositions";
|
||||
import { Script } from "../../../Script/Script";
|
||||
import { isEmpty } from "lodash";
|
||||
|
||||
interface EditorParameters {
|
||||
terminal: ITerminal;
|
||||
@ -22,6 +25,74 @@ export async function main(ns) {
|
||||
|
||||
}`;
|
||||
|
||||
interface ISimpleScriptGlob {
|
||||
glob: string;
|
||||
preGlob: string;
|
||||
postGlob: string;
|
||||
globError: string;
|
||||
globMatches: string[];
|
||||
globAgainst: Script[];
|
||||
}
|
||||
|
||||
function containsSimpleGlob(filename: string): boolean {
|
||||
return filename.includes("*");
|
||||
}
|
||||
|
||||
function detectSimpleScriptGlob(
|
||||
args: EditorParameters["args"],
|
||||
player: IPlayer,
|
||||
terminal: ITerminal,
|
||||
): ISimpleScriptGlob | null {
|
||||
if (args.length == 1 && containsSimpleGlob(`${args[0]}`)) {
|
||||
const filename = `${args[0]}`;
|
||||
const scripts = player.getCurrentServer().scripts;
|
||||
const parsedGlob = parseSimpleScriptGlob(filename, scripts, terminal);
|
||||
return parsedGlob;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function parseSimpleScriptGlob(globString: string, globDatabase: Script[], terminal: ITerminal): ISimpleScriptGlob {
|
||||
const parsedGlob: ISimpleScriptGlob = {
|
||||
glob: globString,
|
||||
preGlob: "",
|
||||
postGlob: "",
|
||||
globError: "",
|
||||
globMatches: [],
|
||||
globAgainst: globDatabase,
|
||||
};
|
||||
|
||||
// Ensure deep globs are minified to simple globs, which act as deep globs in this impl
|
||||
globString = globString.replace("**", "*");
|
||||
|
||||
// Ensure only a single glob is present
|
||||
if (globString.split("").filter((c) => c == "*").length !== 1) {
|
||||
parsedGlob.globError = "Only a single glob is supported per command.\nexample: `nano my-dir/*.js`";
|
||||
return parsedGlob;
|
||||
}
|
||||
|
||||
// Split arg around glob, normalize preGlob path
|
||||
[parsedGlob.preGlob, parsedGlob.postGlob] = globString.split("*");
|
||||
parsedGlob.preGlob = removeLeadingSlash(parsedGlob.preGlob);
|
||||
|
||||
// Add CWD to preGlob path
|
||||
const cwd = removeTrailingSlash(terminal.cwd())
|
||||
parsedGlob.preGlob = `${cwd}/${parsedGlob.preGlob}`
|
||||
|
||||
// For every script on the current server, filter matched scripts per glob values & persist
|
||||
globDatabase.forEach((script) => {
|
||||
const filename = script.filename.startsWith('/') ? script.filename : `/${script.filename}`
|
||||
if (filename.startsWith(parsedGlob.preGlob) && filename.endsWith(parsedGlob.postGlob)) {
|
||||
parsedGlob.globMatches.push(filename);
|
||||
}
|
||||
});
|
||||
|
||||
// Rebuild glob for potential error reporting
|
||||
parsedGlob.glob = `${parsedGlob.preGlob}*${parsedGlob.postGlob}`
|
||||
|
||||
return parsedGlob;
|
||||
}
|
||||
|
||||
export function commonEditor(
|
||||
command: string,
|
||||
{ terminal, router, player, args }: EditorParameters,
|
||||
@ -32,8 +103,15 @@ export function commonEditor(
|
||||
return;
|
||||
}
|
||||
|
||||
let filesToLoadOrCreate = args;
|
||||
try {
|
||||
const files = args.map((arg) => {
|
||||
const globSearch = detectSimpleScriptGlob(args, player, terminal);
|
||||
if (globSearch) {
|
||||
if (isEmpty(globSearch.globError) === false) throw new Error(globSearch.globError);
|
||||
filesToLoadOrCreate = globSearch.globMatches;
|
||||
}
|
||||
|
||||
const files = filesToLoadOrCreate.map((arg) => {
|
||||
const filename = `${arg}`;
|
||||
|
||||
if (isScriptFilename(filename)) {
|
||||
@ -55,12 +133,18 @@ export function commonEditor(
|
||||
if (filename.endsWith(".txt")) {
|
||||
const filepath = terminal.getFilepath(filename);
|
||||
const txt = terminal.getTextFile(player, filename);
|
||||
return [filepath, txt == null ? "" : txt.text];
|
||||
return [filepath, txt === null ? "" : txt.text];
|
||||
}
|
||||
|
||||
throw new Error(`Invalid file. Only scripts (.script, .ns, .js), or text files (.txt) can be edited with ${command}`);
|
||||
throw new Error(
|
||||
`Invalid file. Only scripts (.script, .ns, .js), or text files (.txt) can be edited with ${command}`,
|
||||
);
|
||||
});
|
||||
|
||||
if (globSearch && files.length === 0) {
|
||||
throw new Error(`Could not find any valid files to open with ${command} using glob: \`${globSearch.glob}\``)
|
||||
}
|
||||
|
||||
router.toScriptEditor(Object.fromEntries(files), scriptEditorRouteOptions);
|
||||
} catch (e) {
|
||||
terminal.error(`${e}`);
|
||||
|
@ -1,9 +1,13 @@
|
||||
import { Theme } from "@mui/material/styles";
|
||||
import createStyles from "@mui/styles/createStyles";
|
||||
import makeStyles from "@mui/styles/makeStyles";
|
||||
import { toString } from "lodash";
|
||||
import React from "react";
|
||||
import { ITerminal } from "../ITerminal";
|
||||
import { IRouter } from "../../ui/Router";
|
||||
import { IPlayer } from "../../PersonObjects/IPlayer";
|
||||
import { BaseServer } from "../../Server/BaseServer";
|
||||
import { getFirstParentDirectory, isValidDirectoryPath, evaluateDirectoryPath } from "../../Terminal/DirectoryHelpers";
|
||||
import { evaluateDirectoryPath, getFirstParentDirectory, isValidDirectoryPath } from "../../Terminal/DirectoryHelpers";
|
||||
import { IRouter } from "../../ui/Router";
|
||||
import { ITerminal } from "../ITerminal";
|
||||
|
||||
export function ls(
|
||||
terminal: ITerminal,
|
||||
@ -113,7 +117,55 @@ export function ls(
|
||||
allMessages.sort();
|
||||
folders.sort();
|
||||
|
||||
function postSegments(segments: string[], style?: any): void {
|
||||
interface ClickableScriptRowProps {
|
||||
row: string;
|
||||
prefix: string;
|
||||
hostname: string;
|
||||
}
|
||||
|
||||
function ClickableScriptRow({ row, prefix, hostname }: ClickableScriptRowProps): React.ReactElement {
|
||||
const classes = makeStyles((theme: Theme) =>
|
||||
createStyles({
|
||||
scriptLinksWrap: {
|
||||
display: "flex",
|
||||
color: theme.palette.warning.main,
|
||||
},
|
||||
scriptLink: {
|
||||
cursor: "pointer",
|
||||
textDecorationLine: "underline",
|
||||
paddingRight: "1.15em",
|
||||
"&:last-child": { padding: 0 },
|
||||
},
|
||||
}),
|
||||
)();
|
||||
|
||||
const rowSplit = row
|
||||
.split(" ")
|
||||
.map((x) => x.trim())
|
||||
.filter((x) => !!x);
|
||||
|
||||
function onScriptLinkClick(filename: string): void {
|
||||
if (player.getCurrentServer().hostname !== hostname) {
|
||||
return terminal.error(`File is not on this server, connect to ${hostname} and try again`);
|
||||
}
|
||||
if (filename.startsWith("/")) filename = filename.slice(1);
|
||||
const filepath = terminal.getFilepath(`${prefix}${filename}`);
|
||||
const code = toString(terminal.getScript(player, filepath)?.code);
|
||||
router.toScriptEditor({ [filepath]: code });
|
||||
}
|
||||
|
||||
return (
|
||||
<span className={classes.scriptLinksWrap}>
|
||||
{rowSplit.map((rowItem) => (
|
||||
<span key={rowItem} className={classes.scriptLink} onClick={() => onScriptLinkClick(rowItem)}>
|
||||
{rowItem}
|
||||
</span>
|
||||
))}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
function postSegments(segments: string[], style?: any, linked?: boolean): void {
|
||||
const maxLength = Math.max(...segments.map((s) => s.length)) + 1;
|
||||
const filesPerRow = Math.floor(80 / maxLength);
|
||||
for (let i = 0; i < segments.length; i++) {
|
||||
@ -128,7 +180,11 @@ export function ls(
|
||||
if (!style) {
|
||||
terminal.print(row);
|
||||
} else {
|
||||
terminal.printRaw(<span style={style}>{row}</span>);
|
||||
if (linked) {
|
||||
terminal.printRaw(<ClickableScriptRow row={row} prefix={prefix} hostname={server.hostname} />);
|
||||
} else {
|
||||
terminal.printRaw(<span style={style}>{row}</span>);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -139,9 +195,9 @@ export function ls(
|
||||
{ segments: allTextFiles },
|
||||
{ segments: allPrograms },
|
||||
{ segments: allContracts },
|
||||
{ segments: allScripts, style: { color: "yellow", fontStyle: "bold" } },
|
||||
{ segments: allScripts, style: { color: "yellow", fontStyle: "bold" }, linked: true },
|
||||
].filter((g) => g.segments.length > 0);
|
||||
for (let i = 0; i < groups.length; i++) {
|
||||
postSegments(groups[i].segments, groups[i].style);
|
||||
postSegments(groups[i].segments, groups[i].style, groups[i].linked);
|
||||
}
|
||||
}
|
||||
|
@ -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,31 +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 (input.startsWith("./") && 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]);
|
||||
}
|
||||
|
||||
//Contracts on current server
|
||||
for (let i = 0; i < currServ.contracts.length; ++i) {
|
||||
allPos.push("./" + currServ.contracts[i].fn)
|
||||
}
|
||||
return allPos;
|
||||
}
|
||||
|
||||
// Autocomplete the command
|
||||
if (index === -1) {
|
||||
if (index === -1 && !input.startsWith('./')) {
|
||||
return commands.concat(Object.keys(Aliases)).concat(Object.keys(GlobalAliases));
|
||||
}
|
||||
|
||||
@ -292,14 +270,21 @@ export async function determineAllPossibilitiesForTabCompletion(
|
||||
|
||||
async function scriptAutocomplete(): Promise<string[] | undefined> {
|
||||
if (!isCommand("run") && !isCommand("tail") && !isCommand("kill") && !input.startsWith("./")) return;
|
||||
if (input.startsWith("./")) input = "run " + input.slice(2);
|
||||
const commands = ParseCommands(input);
|
||||
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 || 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);
|
||||
@ -341,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();
|
||||
@ -383,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 {
|
||||
|
@ -279,7 +279,7 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [
|
||||
"i to i+n.",
|
||||
"\n\nAssuming you are initially positioned",
|
||||
"at the start of the array, determine whether you are",
|
||||
"able to reach the last index exactly.\n\n",
|
||||
"able to reach the last index.\n\n",
|
||||
"Your answer should be submitted as 1 or 0, representing true and false respectively",
|
||||
].join(" ");
|
||||
},
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { cloneDeep } from "lodash";
|
||||
|
||||
import { IPlayer } from "../PersonObjects/IPlayer";
|
||||
import { IEngine } from "../IEngine";
|
||||
import { ITerminal } from "../Terminal/ITerminal";
|
||||
@ -87,11 +87,6 @@ interface IProps {
|
||||
engine: IEngine;
|
||||
}
|
||||
|
||||
interface PageHistoryEntry {
|
||||
page: Page;
|
||||
args: any[];
|
||||
}
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) =>
|
||||
createStyles({
|
||||
root: {
|
||||
@ -111,15 +106,6 @@ export let Router: IRouter = {
|
||||
page: () => {
|
||||
throw new Error("Router called before initialization");
|
||||
},
|
||||
previousPage: () => {
|
||||
throw new Error("Router called before initialization");
|
||||
},
|
||||
clearHistory: () => {
|
||||
throw new Error("Router called before initialization");
|
||||
},
|
||||
toPreviousPage: (): boolean => {
|
||||
throw new Error("Router called before initialization");
|
||||
},
|
||||
toActiveScripts: () => {
|
||||
throw new Error("Router called before initialization");
|
||||
},
|
||||
@ -218,9 +204,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 startPage = determineStartPage(player);
|
||||
const [page, setPage] = useState(startPage);
|
||||
const [pageHistory, setPageHistory] = useState<PageHistoryEntry[]>([{ page: startPage, args: [] }]);
|
||||
const [page, setPage] = useState(determineStartPage(player));
|
||||
const setRerender = useState(0)[1];
|
||||
const [faction, setFaction] = useState<Faction>(
|
||||
player.currentWorkFactionName ? Factions[player.currentWorkFactionName] : (undefined as unknown as Faction),
|
||||
@ -256,131 +240,70 @@ export function GameRoot({ player, engine, terminal }: IProps): React.ReactEleme
|
||||
setTimeout(() => htmlLocation.reload(), 2000);
|
||||
}
|
||||
|
||||
function setCurrentPage(page: Page, ...args: any): void {
|
||||
const history = [
|
||||
{ page, args: cloneDeep(args) },
|
||||
...pageHistory
|
||||
].slice(0, 20);
|
||||
setPageHistory(history)
|
||||
setPage(page)
|
||||
}
|
||||
|
||||
function goBack(fallback: (...args: any[]) => void): void {
|
||||
const [ , previousPage ] = pageHistory;
|
||||
if (previousPage) {
|
||||
const handler = pageToRouterMap[previousPage?.page];
|
||||
handler(...previousPage.args);
|
||||
} else {
|
||||
if (fallback) fallback();
|
||||
}
|
||||
const [ , ...history] = pageHistory;
|
||||
setPageHistory(cloneDeep(history));
|
||||
}
|
||||
|
||||
const pageToRouterMap: { [key: number] : (...args: any[]) => void } = {
|
||||
[Page.ActiveScripts]: Router.toActiveScripts,
|
||||
[Page.Augmentations]: Router.toAugmentations,
|
||||
[Page.Bladeburner]: Router.toBladeburner,
|
||||
[Page.Stats]: Router.toStats,
|
||||
[Page.Corporation]: Router.toCorporation,
|
||||
[Page.CreateProgram]: Router.toCreateProgram,
|
||||
[Page.DevMenu]: Router.toDevMenu,
|
||||
[Page.Faction]: Router.toFaction,
|
||||
[Page.Factions]: Router.toFactions,
|
||||
[Page.Options]: Router.toGameOptions,
|
||||
[Page.Gang]: Router.toGang,
|
||||
[Page.Hacknet]: Router.toHacknetNodes,
|
||||
[Page.Milestones]: Router.toMilestones,
|
||||
[Page.Resleeves]: Router.toResleeves,
|
||||
[Page.ScriptEditor]: Router.toScriptEditor,
|
||||
[Page.Sleeves]: Router.toSleeves,
|
||||
[Page.StockMarket]: Router.toStockMarket,
|
||||
[Page.Terminal]: Router.toTerminal,
|
||||
[Page.Tutorial]: Router.toTutorial,
|
||||
[Page.Job]: Router.toJob,
|
||||
[Page.City]: Router.toCity,
|
||||
[Page.Travel]: Router.toTravel,
|
||||
[Page.BitVerse]: Router.toBitVerse,
|
||||
[Page.Infiltration]: Router.toInfiltration,
|
||||
[Page.Work]: Router.toWork,
|
||||
[Page.BladeburnerCinematic]: Router.toBladeburnerCinematic,
|
||||
[Page.Location]: Router.toLocation,
|
||||
[Page.StaneksGift]: Router.toStaneksGift,
|
||||
[Page.Achievements]: Router.toAchievements,
|
||||
}
|
||||
|
||||
Router = {
|
||||
page: () => page,
|
||||
previousPage: () => {
|
||||
const [ , previousPage] = pageHistory;
|
||||
return previousPage?.page ?? -1;
|
||||
},
|
||||
clearHistory: () => setPageHistory([]),
|
||||
toPreviousPage: goBack,
|
||||
toActiveScripts: () => setCurrentPage(Page.ActiveScripts),
|
||||
toAugmentations: () => setCurrentPage(Page.Augmentations),
|
||||
toBladeburner: () => setCurrentPage(Page.Bladeburner),
|
||||
toStats: () => setCurrentPage(Page.Stats),
|
||||
toCorporation: () => setCurrentPage(Page.Corporation),
|
||||
toCreateProgram: () => setCurrentPage(Page.CreateProgram),
|
||||
toDevMenu: () => setCurrentPage(Page.DevMenu),
|
||||
toActiveScripts: () => setPage(Page.ActiveScripts),
|
||||
toAugmentations: () => setPage(Page.Augmentations),
|
||||
toBladeburner: () => setPage(Page.Bladeburner),
|
||||
toStats: () => setPage(Page.Stats),
|
||||
toCorporation: () => setPage(Page.Corporation),
|
||||
toCreateProgram: () => setPage(Page.CreateProgram),
|
||||
toDevMenu: () => setPage(Page.DevMenu),
|
||||
toFaction: (faction?: Faction) => {
|
||||
setCurrentPage(Page.Faction, faction);
|
||||
setPage(Page.Faction);
|
||||
if (faction) setFaction(faction);
|
||||
},
|
||||
toFactions: () => setCurrentPage(Page.Factions),
|
||||
toGameOptions: () => setCurrentPage(Page.Options),
|
||||
toGang: () => setCurrentPage(Page.Gang),
|
||||
toHacknetNodes: () => setCurrentPage(Page.Hacknet),
|
||||
toMilestones: () => setCurrentPage(Page.Milestones),
|
||||
toResleeves: () => setCurrentPage(Page.Resleeves),
|
||||
toFactions: () => setPage(Page.Factions),
|
||||
toGameOptions: () => setPage(Page.Options),
|
||||
toGang: () => setPage(Page.Gang),
|
||||
toHacknetNodes: () => setPage(Page.Hacknet),
|
||||
toMilestones: () => setPage(Page.Milestones),
|
||||
toResleeves: () => setPage(Page.Resleeves),
|
||||
toScriptEditor: (files: Record<string, string>, options?: ScriptEditorRouteOptions) => {
|
||||
setEditorOptions({
|
||||
files,
|
||||
vim: !!options?.vim,
|
||||
});
|
||||
setCurrentPage(Page.ScriptEditor, files, options);
|
||||
setPage(Page.ScriptEditor);
|
||||
},
|
||||
toSleeves: () => setCurrentPage(Page.Sleeves),
|
||||
toStockMarket: () => setCurrentPage(Page.StockMarket),
|
||||
toTerminal: () => setCurrentPage(Page.Terminal),
|
||||
toTutorial: () => setCurrentPage(Page.Tutorial),
|
||||
toSleeves: () => setPage(Page.Sleeves),
|
||||
toStockMarket: () => setPage(Page.StockMarket),
|
||||
toTerminal: () => setPage(Page.Terminal),
|
||||
toTutorial: () => setPage(Page.Tutorial),
|
||||
toJob: () => {
|
||||
setLocation(Locations[player.companyName]);
|
||||
setCurrentPage(Page.Job);
|
||||
setPage(Page.Job);
|
||||
},
|
||||
toCity: () => {
|
||||
setCurrentPage(Page.City);
|
||||
setPage(Page.City);
|
||||
},
|
||||
toTravel: () => {
|
||||
player.gotoLocation(LocationName.TravelAgency);
|
||||
setCurrentPage(Page.Travel);
|
||||
setPage(Page.Travel);
|
||||
},
|
||||
toBitVerse: (flume: boolean, quick: boolean) => {
|
||||
setFlume(flume);
|
||||
setQuick(quick);
|
||||
setCurrentPage(Page.BitVerse, flume, quick);
|
||||
setPage(Page.BitVerse);
|
||||
},
|
||||
toInfiltration: (location: Location) => {
|
||||
setLocation(location);
|
||||
setCurrentPage(Page.Infiltration, location);
|
||||
},
|
||||
toWork: () => {
|
||||
setCurrentPage(Page.Work);
|
||||
setPage(Page.Infiltration);
|
||||
},
|
||||
toWork: () => setPage(Page.Work),
|
||||
toBladeburnerCinematic: () => {
|
||||
setCurrentPage(Page.BladeburnerCinematic);
|
||||
setPage(Page.BladeburnerCinematic);
|
||||
setCinematicText(cinematicText);
|
||||
},
|
||||
toLocation: (location: Location) => {
|
||||
setLocation(location);
|
||||
setCurrentPage(Page.Location, location);
|
||||
setPage(Page.Location);
|
||||
},
|
||||
toStaneksGift: () => {
|
||||
setCurrentPage(Page.StaneksGift);
|
||||
setPage(Page.StaneksGift);
|
||||
},
|
||||
toAchievements: () => {
|
||||
setCurrentPage(Page.Achievements);
|
||||
setPage(Page.Achievements);
|
||||
},
|
||||
};
|
||||
|
||||
@ -584,23 +507,10 @@ export function GameRoot({ player, engine, terminal }: IProps): React.ReactEleme
|
||||
<Context.Player.Provider value={player}>
|
||||
<Context.Router.Provider value={Router}>
|
||||
<ErrorBoundary key={errorBoundaryKey} router={Router} softReset={softReset}>
|
||||
<SnackbarProvider>
|
||||
<Overview mode={ITutorial.isRunning ? "tutorial" : "overview"}>
|
||||
{!ITutorial.isRunning ? (
|
||||
<CharacterOverview
|
||||
save={() => saveObject.saveGame()}
|
||||
killScripts={killAllScripts}
|
||||
router={Router}
|
||||
allowBackButton={withSidebar} />
|
||||
) : (
|
||||
<InteractiveTutorialRoot />
|
||||
)}
|
||||
</Overview>
|
||||
{withSidebar ? (
|
||||
<Box display="flex" flexDirection="row" width="100%">
|
||||
<SidebarRoot player={player} router={Router} page={page} />
|
||||
<Box className={classes.root}>{mainPage}</Box>
|
||||
</Box>
|
||||
<SnackbarProvider>
|
||||
<Overview mode={ITutorial.isRunning ? "tutorial" : "overview"}>
|
||||
{!ITutorial.isRunning ? (
|
||||
<CharacterOverview save={() => saveObject.saveGame()} killScripts={killAllScripts} />
|
||||
) : (
|
||||
<Box className={classes.root}>{mainPage}</Box>
|
||||
)}
|
||||
|
@ -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";
|
||||
@ -16,21 +17,19 @@ import Typography from "@mui/material/Typography";
|
||||
import Button from "@mui/material/Button";
|
||||
import IconButton from "@mui/material/IconButton";
|
||||
import SaveIcon from "@mui/icons-material/Save";
|
||||
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
|
||||
import ClearAllIcon from "@mui/icons-material/ClearAll";
|
||||
|
||||
import { Settings } from "../../Settings/Settings";
|
||||
import { use } from "../Context";
|
||||
import { StatsProgressOverviewCell } from "./StatsProgressBar";
|
||||
import { BitNodeMultipliers } from "../../BitNode/BitNodeMultipliers";
|
||||
import { IRouter, Page } from "../Router";
|
||||
|
||||
import { Box, Tooltip } from "@mui/material";
|
||||
import { CONSTANTS } from "../../Constants";
|
||||
|
||||
interface IProps {
|
||||
save: () => void;
|
||||
killScripts: () => void;
|
||||
router: IRouter;
|
||||
allowBackButton: boolean;
|
||||
}
|
||||
|
||||
function Intelligence(): React.ReactElement {
|
||||
@ -79,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>
|
||||
@ -176,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,
|
||||
@ -210,7 +236,7 @@ const useStyles = makeStyles((theme: Theme) =>
|
||||
|
||||
export { useStyles as characterOverviewStyles };
|
||||
|
||||
export function CharacterOverview({ save, killScripts, router, allowBackButton }: IProps): React.ReactElement {
|
||||
export function CharacterOverview({ save, killScripts }: IProps): React.ReactElement {
|
||||
const [killOpen, setKillOpen] = useState(false);
|
||||
const player = use.Player();
|
||||
|
||||
@ -249,9 +275,6 @@ export function CharacterOverview({ save, killScripts, router, allowBackButton }
|
||||
player.charisma_mult * BitNodeMultipliers.CharismaLevelMultiplier,
|
||||
);
|
||||
|
||||
const previousPageName = router.previousPage() < 0
|
||||
? '' : Page[router.previousPage() ?? 0].replace(/([a-z])([A-Z])/g, '$1 $2');
|
||||
|
||||
return (
|
||||
<>
|
||||
<Table sx={{ display: "block", m: 1 }}>
|
||||
@ -428,24 +451,15 @@ export function CharacterOverview({ save, killScripts, router, allowBackButton }
|
||||
<Bladeburner />
|
||||
</TableBody>
|
||||
</Table>
|
||||
<Box sx={{ display: 'flex', borderTop: `1px solid ${Settings.theme.welllight}` }}>
|
||||
<Box sx={{ display: 'flex', flex: 1, justifyContent: 'flex-start', alignItems: 'center' }}>
|
||||
<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>
|
||||
{allowBackButton && (
|
||||
<IconButton
|
||||
disabled={!previousPageName}
|
||||
onClick={() => router.toPreviousPage()}>
|
||||
<Tooltip title={previousPageName ? `Go back to "${previousPageName}"` : ''}>
|
||||
<ArrowBackIcon />
|
||||
</Tooltip>
|
||||
</IconButton>
|
||||
)}
|
||||
</Box>
|
||||
<Box sx={{ display: 'flex', flex: 1, justifyContent: 'flex-end', alignItems: 'center' }}>
|
||||
<Box sx={{ display: "flex", flex: 1, justifyContent: "flex-end", alignItems: "center" }}>
|
||||
<IconButton onClick={() => setKillOpen(true)}>
|
||||
<Tooltip title="Kill all running scripts">
|
||||
<ClearAllIcon color="error" />
|
||||
|
@ -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));
|
||||
};
|
||||
}, []);
|
||||
|
||||
|
@ -529,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
|
||||
@ -571,7 +571,7 @@ export function GameOptionsRoot(props: IProps): React.ReactElement {
|
||||
}
|
||||
/>
|
||||
</Box>
|
||||
<Box>
|
||||
<Box sx={{ display: 'grid' }}>
|
||||
<Tooltip
|
||||
title={
|
||||
<Typography>
|
||||
@ -586,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>
|
||||
@ -613,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>
|
||||
@ -637,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();
|
||||
})
|
||||
}),
|
||||
[],
|
||||
);
|
||||
|
@ -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>
|
||||
|
@ -53,9 +53,6 @@ export interface IRouter {
|
||||
// toRedPill(): void;
|
||||
// toworkInProgress(): void;
|
||||
page(): Page;
|
||||
previousPage(): Page;
|
||||
clearHistory(): void;
|
||||
toPreviousPage(fallback?: (...args: any[]) => void): void;
|
||||
toActiveScripts(): void;
|
||||
toAugmentations(): void;
|
||||
toBitVerse(flume: boolean, quick: boolean): void;
|
||||
|
@ -36,12 +36,12 @@ export function WorkInProgressRoot(): React.ReactElement {
|
||||
const faction = Factions[player.currentWorkFactionName];
|
||||
if (player.workType == CONSTANTS.WorkTypeFaction) {
|
||||
function cancel(): void {
|
||||
router.toFaction(faction);
|
||||
player.finishFactionWork(true);
|
||||
router.toPreviousPage(() => router.toFaction(faction));
|
||||
}
|
||||
function unfocus(): void {
|
||||
router.toFaction(faction);
|
||||
player.stopFocusing();
|
||||
router.toPreviousPage(() => router.toFaction(faction));
|
||||
}
|
||||
return (
|
||||
<Grid container direction="column" justifyContent="center" alignItems="center" style={{ minHeight: "100vh" }}>
|
||||
@ -120,12 +120,13 @@ export function WorkInProgressRoot(): React.ReactElement {
|
||||
if (player.className !== "") {
|
||||
function cancel(): void {
|
||||
player.finishClass(true);
|
||||
router.toPreviousPage(() => router.toCity());
|
||||
router.toCity();
|
||||
}
|
||||
|
||||
function unfocus(): void {
|
||||
router.toFaction(faction);
|
||||
router.toCity();
|
||||
player.stopFocusing();
|
||||
router.toPreviousPage(() => router.toCity());
|
||||
}
|
||||
|
||||
let stopText = "";
|
||||
@ -211,11 +212,11 @@ export function WorkInProgressRoot(): React.ReactElement {
|
||||
|
||||
function cancel(): void {
|
||||
player.finishWork(true);
|
||||
router.toPreviousPage(() => router.toJob());
|
||||
router.toJob();
|
||||
}
|
||||
function unfocus(): void {
|
||||
player.stopFocusing();
|
||||
router.toPreviousPage(() => router.toJob());
|
||||
router.toJob();
|
||||
}
|
||||
|
||||
const position = player.jobs[player.companyName];
|
||||
@ -303,11 +304,11 @@ export function WorkInProgressRoot(): React.ReactElement {
|
||||
if (player.workType == CONSTANTS.WorkTypeCompanyPartTime) {
|
||||
function cancel(): void {
|
||||
player.finishWorkPartTime(true);
|
||||
router.toPreviousPage(() => router.toJob());
|
||||
router.toJob();
|
||||
}
|
||||
function unfocus(): void {
|
||||
player.stopFocusing();
|
||||
router.toPreviousPage(() => router.toJob());
|
||||
router.toJob();
|
||||
}
|
||||
const comp = Companies[player.companyName];
|
||||
let companyRep = 0;
|
||||
@ -439,11 +440,11 @@ export function WorkInProgressRoot(): React.ReactElement {
|
||||
if (player.createProgramName !== "") {
|
||||
function cancel(): void {
|
||||
player.finishCreateProgramWork(true);
|
||||
router.toPreviousPage(() => router.toTerminal());
|
||||
router.toTerminal();
|
||||
}
|
||||
function unfocus(): void {
|
||||
router.toTerminal();
|
||||
player.stopFocusing();
|
||||
router.toPreviousPage(() => router.toTerminal());
|
||||
}
|
||||
return (
|
||||
<Grid container direction="column" justifyContent="center" alignItems="center" style={{ minHeight: "100vh" }}>
|
||||
|
@ -1,5 +1,7 @@
|
||||
/* Generic Reviver, toJSON, and fromJSON functions used for saving and loading objects */
|
||||
|
||||
import { validateObject } from "./Validator";
|
||||
|
||||
interface IReviverValue {
|
||||
ctor: string;
|
||||
data: any;
|
||||
@ -26,7 +28,11 @@ export function Reviver(key: string, value: IReviverValue | null): any {
|
||||
const ctor = Reviver.constructors[value.ctor];
|
||||
|
||||
if (typeof ctor === "function" && typeof ctor.fromJSON === "function") {
|
||||
return ctor.fromJSON(value);
|
||||
const obj = ctor.fromJSON(value);
|
||||
if (ctor.validationData !== undefined) {
|
||||
validateObject(obj, ctor.validationData);
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
return value;
|
||||
|
78
src/utils/Validator.ts
Normal file
78
src/utils/Validator.ts
Normal file
@ -0,0 +1,78 @@
|
||||
export type ObjectValidator<T> = {
|
||||
[key in keyof T]?: ParameterValidator<T, keyof T>;
|
||||
}
|
||||
|
||||
interface ParameterValidatorObject<Type, Key extends keyof Type> {
|
||||
default?: any;
|
||||
min?: number;
|
||||
max?: number;
|
||||
func?: (obj: Type, validator: ObjectValidator<Type>, key: Key) => void;
|
||||
}
|
||||
type ParameterValidatorFunction<Type, Key extends keyof Type> = (obj: Type, key: Key) => void;
|
||||
type ParameterValidator<Type, Key extends keyof Type> = ParameterValidatorObject<Type, Key> | ParameterValidatorFunction<Type, Key>
|
||||
|
||||
export function validateObject<Type extends Record<string, unknown>, Key extends keyof Type>(obj: Type, validator: ObjectValidator<Type>): void {
|
||||
for (const key of Object.keys(validator) as Key[]) {
|
||||
const paramValidator = validator[key];
|
||||
if (paramValidator !== undefined) {
|
||||
if (typeof paramValidator === 'function') {
|
||||
paramValidator(obj, key);
|
||||
} else {
|
||||
if (paramValidator.func !== undefined) {
|
||||
paramValidator.func(obj, validator, key);
|
||||
} else {
|
||||
if ((typeof obj[key]) !== (typeof paramValidator.default)) {
|
||||
obj[key] = paramValidator.default
|
||||
}
|
||||
if (typeof obj[key] === 'number' && paramValidator.min !== undefined) {
|
||||
if (obj[key] < paramValidator.min) obj[key] = paramValidator.min as Type[Key];
|
||||
}
|
||||
if (typeof obj[key] === 'number' && paramValidator.max !== undefined) {
|
||||
if (obj[key] > paramValidator.max) obj[key] = paramValidator.max as Type[Key];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function minMax<Type, Key extends keyof Type>(def: number, min: number, max: number): (obj: Type, key: Key & keyof Type) => void {
|
||||
return (obj, key) => {
|
||||
if (typeof obj[key] !== 'number') {
|
||||
obj[key] = def as unknown as Type[Key];
|
||||
return;
|
||||
}
|
||||
if ((obj[key] as unknown as number) < min) {
|
||||
obj[key] = min as unknown as Type[Key];
|
||||
}
|
||||
if ((obj[key] as unknown as number) > max) {
|
||||
obj[key] = max as unknown as Type[Key];
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function oneOf<Type, Key extends keyof Type, Value>(def: Value, options: Value[]): (obj: Type, key: Key & keyof Type) => void {
|
||||
return (obj, key) => {
|
||||
if (typeof obj[key] !== typeof def) {
|
||||
obj[key] = def as unknown as Type[Key];
|
||||
return;
|
||||
}
|
||||
if (!options.includes(obj[key] as unknown as Value)) {
|
||||
obj[key] = def as unknown as Type[Key];
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function subsetOf<Type, Key extends keyof Type, Value>(options: Value[]): (obj: Type, key: Key & keyof Type) => void {
|
||||
return (obj, key) => {
|
||||
if (typeof obj[key] !== 'object' || !Array.isArray(obj[key])) {
|
||||
obj[key] = [] as unknown as Type[Key];
|
||||
return;
|
||||
}
|
||||
const validValues: Value[] = [];
|
||||
for (const value of obj[key] as unknown as Value[]) {
|
||||
if (options.includes(value)) validValues.push(value);
|
||||
}
|
||||
obj[key] = validValues as unknown as Type[Key];
|
||||
};
|
||||
}
|
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)
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue
Block a user