mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2024-11-18 05:33:54 +01:00
Merge branch 'dev' into feat/add-vim-mode
This commit is contained in:
commit
d372166c8b
@ -2,12 +2,9 @@ root = true
|
||||
|
||||
[*]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
indent_size = 2
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[package.json]
|
||||
indent_size = 2
|
||||
|
||||
[md]
|
||||
trim_trailing_whitespace = false
|
||||
|
@ -1,7 +1,27 @@
|
||||
node_modules/
|
||||
doc/build/
|
||||
dist/
|
||||
tests/*.bundle.*
|
||||
input/
|
||||
|
||||
.dist
|
||||
.tmp
|
||||
.package
|
||||
|
||||
assets/
|
||||
css/
|
||||
.cypress/
|
||||
cypress/
|
||||
doc/
|
||||
markdown/
|
||||
netscript_tests/
|
||||
scripts/
|
||||
|
||||
electron/lib
|
||||
electron/greenworks.js
|
||||
src/ThirdParty/*
|
||||
src/JSInterpreter.js
|
||||
main.bundle.js
|
||||
|
||||
test/*.bundle.*
|
||||
editor.main.js
|
||||
main.bundle.js
|
||||
webpack.config.js
|
||||
webpack.config-test.js
|
||||
|
51
.github/workflows/ci.yml
vendored
Normal file
51
.github/workflows/ci.yml
vendored
Normal file
@ -0,0 +1,51 @@
|
||||
# This is a basic workflow to help you get started with Actions
|
||||
name: CI
|
||||
|
||||
on:
|
||||
# Triggers the workflow on push or pull request events but only for the dev branch
|
||||
push:
|
||||
branches: [ dev ]
|
||||
pull_request:
|
||||
branches: [ dev ]
|
||||
|
||||
# Allows you to run this workflow manually from the Actions tab
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
# The type of runner that the job will run on
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [16.13.1]
|
||||
|
||||
steps:
|
||||
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
|
||||
- run: echo "The job was automatically triggered by a ${{ github.event_name }} event."
|
||||
- run: echo "This job is now running on a ${{ runner.os }} server hosted by GitHub!"
|
||||
- run: echo "The name of your branch is ${{ github.ref }} and your repository is ${{ github.repository }}."
|
||||
- uses: actions/checkout@v2
|
||||
- run: echo "The ${{ github.repository }} repository has been cloned to the runner."
|
||||
- run: echo "The workflow is now ready to test your code on the runner."
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
- name: Install npm dependencies
|
||||
run: npm ci
|
||||
- name: Run unit tests
|
||||
run: npm run test
|
||||
- name: Run linter
|
||||
run: npm run lint:report
|
||||
- name: Build the web app
|
||||
run: npm run build
|
||||
|
||||
# Cannot build the electron app by default using ubuntu
|
||||
# Wrapper command 'wine64' not found on the system. Consult your Linux distribution's package manager to determine how to install Wine.
|
||||
# - name: Build the electron app
|
||||
# run: ./package.sh
|
||||
|
||||
# Unable to properly run the cypress tests for now, they are throwing errors.
|
||||
# - name: Run the integration tests
|
||||
# run: npm run cy:test
|
1
.nojekyll
Normal file
1
.nojekyll
Normal file
@ -0,0 +1 @@
|
||||
|
152
dist/vendor.bundle.js
vendored
152
dist/vendor.bundle.js
vendored
File diff suppressed because one or more lines are too long
@ -18,7 +18,7 @@ Sleeve technology is unlocked in :ref:`BitNode-10 <gameplay_bitnodes>`.
|
||||
|
||||
Duplicate Sleeves
|
||||
^^^^^^^^^^^^^^^^^
|
||||
Duplicate Sleeves are MK-V Synthoids (synthetic androids) into which your consciuosness
|
||||
Duplicate Sleeves are MK-V Synthoids (synthetic androids) into which your consciousness
|
||||
has been copied. In other words, these Synthoids contain a perfect duplicate of your mind.
|
||||
|
||||
Duplicate Sleeves are essentially clones which you can use to perform work-type actions,
|
||||
|
@ -4,7 +4,7 @@
|
||||
|
||||
Source-Files
|
||||
============
|
||||
Source-Files are a type of persistent upgrade that are more powerful than Augmentations.
|
||||
Source-Files are a type of persistent upgrade that is more powerful than Augmentations.
|
||||
Source-Files are received by destroying a BitNode. There are many different BitNodes
|
||||
in the game and each BitNode will grant a different Source-File when it is destroyed.
|
||||
|
||||
@ -15,19 +15,19 @@ of level 3.
|
||||
List of all Source-Files
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
+-------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
|| BitNode-1: Source Genesis || * Lets the player start with 32 GB of RAM on home computer. |
|
||||
|| BitNode-1: Source Genesis || * Let the player start with 32 GB of RAM on the home computer. |
|
||||
|| || * Increases all of the player's multipliers by 16%/24%/28%. |
|
||||
+-------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
|| BitNode-2: Rise of the Underworld || * Lets the player create Gangs in other BitNodes (although some |
|
||||
|| BitNode-2: Rise of the Underworld || * Let the player create Gangs in other BitNodes (although some |
|
||||
|| || BitNodes will disable this mechanic). |
|
||||
|| || * Increases the player's crime success rate, crime money, and |
|
||||
|| || charisma multipliers by 24%/36%/42%. |
|
||||
+-------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
|| BitNode-3: Corporatocracy || * Lets the player create Corporations in other BitNodes (although some |
|
||||
|| BitNode-3: Corporatocracy || * Let the player create Corporations in other BitNodes (although some |
|
||||
|| || BitNodes will disable this mechanic). |
|
||||
|| || * Increases the player's charisma and company salary multipliers by 8%/12%/14%. |
|
||||
+-------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
|| BitNode-4: The Singularity || * Lets the player access and use Netscript Singularity Functions in other BitNodes. |
|
||||
|| BitNode-4: The Singularity || * Let the player access and use Netscript Singularity Functions in other BitNodes. |
|
||||
|| || * Each level of this Source-File opens up more of the Singularity Functions to use. |
|
||||
+-------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
|| BitNode-5: Artificial Intelligence || * Unlocks :ref:`gameplay_intelligence`. |
|
||||
@ -47,7 +47,7 @@ List of all Source-Files
|
||||
|| || * Level 3 grants permanent access to use limit/stop orders. |
|
||||
+-------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
|| BitNode-9: Hacktocracy || * Level 1 permanently unlocks the Hacknet Server in other BitNodes. |
|
||||
|| || * Level 2 lets the player start with 128 GB of RAM on home computer. |
|
||||
|| || * Level 2 lets the player start with 128 GB of RAM on the home computer. |
|
||||
|| || * Level 3 grants a highly-upgraded Hacknet Server when entering a new BitNode (it |
|
||||
|| || will be lost after installing augments). |
|
||||
+-------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
@ -60,6 +60,6 @@ List of all Source-Files
|
||||
|| || 32%/48%/56%. |
|
||||
+-------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
|| BitNode-12: The Recursion || * There is no maximum level for this Source-File. |
|
||||
|| || * Lets the player start with Neuroflux Governor equal to the level of this |
|
||||
|| || * Let the player start with Neuroflux Governor equal to the level of this |
|
||||
|| || Source-File. |
|
||||
+-------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
+-------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
@ -3,8 +3,107 @@
|
||||
Changelog
|
||||
=========
|
||||
|
||||
v1.1.0 - 2021-12-18 You guys are awesome (community because they're god damn awesome)
|
||||
-------------------------------------------------------------------------------------
|
||||
|
||||
** Script Editor **
|
||||
|
||||
* The text editor can open several files at once. (@Rez855 / @Shadow72)
|
||||
It's not perfect so keep the feedback coming.
|
||||
|
||||
** Steam **
|
||||
|
||||
* Windows has a new launch option that lets player start with killing all their scripts
|
||||
This is a safety net in case all the other safety nets fail.
|
||||
* Linux has several launch options that use different flags for different OS.
|
||||
* Debug and Fullscreen are available in the window utility bar.
|
||||
* Tried (and maybe failed) to make the game completely kill itself after closing.
|
||||
This one I still don't know wtf is going.
|
||||
* No longer has background throttling.
|
||||
* Default color should be pitch black when loading
|
||||
* Add BN13: Challenge achievement.
|
||||
|
||||
** Tutorial **
|
||||
|
||||
* I watched someone play bitburner on youtube and reworked part of
|
||||
the tutorial to try to make some parts of the game clearer.
|
||||
https://www.youtube.com/watch?v=-_JETXff4Zo
|
||||
* Add option to restart tutorial.
|
||||
|
||||
** Netscript **
|
||||
|
||||
* getGangInformation returns more information.
|
||||
* getAscensionResult added
|
||||
* getMemberInformation returns more info
|
||||
* Formulas API has new functions for gang.
|
||||
* Added documentation for corp API.
|
||||
* exec has clearer error message when you send invalid data.
|
||||
* getServer returns all defined field for hacknet servers.
|
||||
* Fix a bug with scp multiple files (@theit8514)
|
||||
* Stack traces should be smarter at replacing blobs with filenames
|
||||
* Fix a weird error message that would occur when throwing raw strings.
|
||||
* Fix shortcuts not working.
|
||||
* Re-added setFocus and isFocused (@theit8514)
|
||||
* new function getHashUpgrades (@MartinFournier)
|
||||
* enableLog accepts "ALL" like disableLog (@wynro)
|
||||
* toast() doesn't crash on invalid data (@ivanjermakov)
|
||||
* alert() doesn't crash on invalid data (@Siern)
|
||||
* Fixed an issue where scripts don't run where they should.
|
||||
* Sleeve getInformation now returns cha
|
||||
* getServer does work with no argument now
|
||||
* workForFaction returns false when it mistakenly returned null
|
||||
|
||||
** Character Overview **
|
||||
|
||||
* The character overview now shows the amount of exp needed to next level (@MartinFournier)
|
||||
|
||||
** Misc. **
|
||||
|
||||
* Add option to supress Game Saved! toasts (@MartinFournier)
|
||||
* Fix bug where ctrl+alt+j was eaten by the wrong process. (@billyvg)
|
||||
* Theme Editor lets you paste colors (@MartinFournier)
|
||||
* ctrl + u/k/w should work on terminal (@billyvg)
|
||||
* Game now shows commit number, this is mostly for me. (@MartinFourier)
|
||||
* running a bad script will give a clearer error message (@TheCoderJT)
|
||||
* Default terminal capacity is maximum (@SayntGarmo)
|
||||
* Fix problems with cp and mv (@theit8514)
|
||||
* Make monaco load fully offline for players behind firewalls.
|
||||
* change beginer guide to use n00dles instead of foodnstuff
|
||||
* BN13 is harder
|
||||
* nerf int gain from manualHack
|
||||
* Fix UI displaying wrong stats (@DJMatch3000)
|
||||
* Fix button not disabling as it should.
|
||||
* New location in Ishima.
|
||||
* Add setting to suppress stock market popups.
|
||||
* Typo fixes (@Hedrauta, @cvr-119, @Ationi, @millennIumAMbiguity
|
||||
@TealKoi, @TheCoderJT, @cblte, @2PacIsAlive, @MageKing17,
|
||||
@Xynrati, @Adraxas, @pobiega)
|
||||
* Fix 100% territory achievement.
|
||||
* Reword message on active scripts page.
|
||||
* Fix terminal not clearing after BN
|
||||
* Remove references to .fconf
|
||||
* Augmentation pages shows BN difficulty with SF5
|
||||
* Fix scripts saving on wrong server while 'connect'ing
|
||||
* Fix gym discount not working.
|
||||
* Fix scan-analyze not working with timestamps
|
||||
* Hash upgrades remember last choice.
|
||||
* Save files now sort by date
|
||||
* The covenant no longer supports negative memory purchases
|
||||
* Fix corp shares buyback triggering by pressing enter
|
||||
* Staneks gift display avg / num charges
|
||||
* Infiltration rewards no longer decay with better stats
|
||||
* terminal 'true' is parsed as boolean not string
|
||||
* tail and kill use autocomplete()
|
||||
* Fix focus for coding contract
|
||||
* massive boost to noodle bar.
|
||||
|
||||
** Special Thanks **
|
||||
|
||||
* Special thank you to everyone on Discord who can answer
|
||||
new player questions so I can focus on more important things.
|
||||
|
||||
v1.1.0 - 2021-12-03 BN13: They're Lunatics (hydroflame & community)
|
||||
-------------------------------------------------------
|
||||
-------------------------------------------------------------------
|
||||
|
||||
** BN13: They're Lunatics **
|
||||
|
||||
|
@ -64,9 +64,9 @@ documentation_title = '{0} Documentation'.format(project)
|
||||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = '1.1'
|
||||
version = '1.2'
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = '1.1.0'
|
||||
release = '1.2.0'
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
|
@ -196,55 +196,60 @@ Here's what mine showed at the time I made this::
|
||||
[home ~]> scan-analyze 2
|
||||
~~~~~~~~~~ Beginning scan-analyze ~~~~~~~~~~
|
||||
|
||||
>n00dles
|
||||
--Root Access: NO, Required hacking skill: 1
|
||||
n00dles
|
||||
--Root Access: YES, Required hacking skill: 1
|
||||
--Number of open ports required to NUKE: 0
|
||||
--RAM: 16
|
||||
|
||||
>sigma-cosmetics
|
||||
--Root Access: NO, Required hacking skill: 5
|
||||
--Number of open ports required to NUKE: 0
|
||||
--RAM: 16
|
||||
|
||||
>joesguns
|
||||
--Root Access: NO, Required hacking skill: 10
|
||||
--Number of open ports required to NUKE: 0
|
||||
--RAM: 16
|
||||
|
||||
---->max-hardware
|
||||
------Root Access: NO, Required hacking skill: 80
|
||||
------Number of open ports required to NUKE: 1
|
||||
------RAM: 32
|
||||
|
||||
>hong-fang-tea
|
||||
--Root Access: NO, Required hacking skill: 30
|
||||
--Number of open ports required to NUKE: 0
|
||||
--RAM: 16
|
||||
|
||||
---->nectar-net
|
||||
------Root Access: NO, Required hacking skill: 20
|
||||
------Number of open ports required to NUKE: 0
|
||||
------RAM: 16
|
||||
|
||||
>harakiri-sushi
|
||||
--Root Access: NO, Required hacking skill: 40
|
||||
--Number of open ports required to NUKE: 0
|
||||
--RAM: 16
|
||||
|
||||
>iron-gym
|
||||
--Root Access: NO, Required hacking skill: 100
|
||||
--Number of open ports required to NUKE: 1
|
||||
--RAM: 32
|
||||
|
||||
---->zer0
|
||||
--RAM: 4.00GB
|
||||
|
||||
----zer0
|
||||
------Root Access: NO, Required hacking skill: 75
|
||||
------Number of open ports required to NUKE: 1
|
||||
------RAM: 32
|
||||
|
||||
---->CSEC
|
||||
------RAM: 32.00GB
|
||||
|
||||
foodnstuff
|
||||
--Root Access: NO, Required hacking skill: 1
|
||||
--Number of open ports required to NUKE: 0
|
||||
--RAM: 16.00GB
|
||||
|
||||
sigma-cosmetics
|
||||
--Root Access: NO, Required hacking skill: 5
|
||||
--Number of open ports required to NUKE: 0
|
||||
--RAM: 16.00GB
|
||||
|
||||
joesguns
|
||||
--Root Access: NO, Required hacking skill: 10
|
||||
--Number of open ports required to NUKE: 0
|
||||
--RAM: 16.00GB
|
||||
|
||||
----max-hardware
|
||||
------Root Access: NO, Required hacking skill: 80
|
||||
------Number of open ports required to NUKE: 1
|
||||
------RAM: 32.00GB
|
||||
|
||||
----CSEC
|
||||
------Root Access: NO, Required hacking skill: 54
|
||||
------Number of open ports required to NUKE: 1
|
||||
------RAM: 8
|
||||
------RAM: 8.00GB
|
||||
|
||||
hong-fang-tea
|
||||
--Root Access: NO, Required hacking skill: 30
|
||||
--Number of open ports required to NUKE: 0
|
||||
--RAM: 16.00GB
|
||||
|
||||
----nectar-net
|
||||
------Root Access: NO, Required hacking skill: 20
|
||||
------Number of open ports required to NUKE: 0
|
||||
------RAM: 16.00GB
|
||||
|
||||
harakiri-sushi
|
||||
--Root Access: NO, Required hacking skill: 40
|
||||
--Number of open ports required to NUKE: 0
|
||||
--RAM: 16.00GB
|
||||
|
||||
iron-gym
|
||||
--Root Access: NO, Required hacking skill: 100
|
||||
--Number of open ports required to NUKE: 1
|
||||
--RAM: 32.00GB
|
||||
|
||||
Take note of the following servers:
|
||||
|
||||
@ -287,7 +292,7 @@ Here's the sequence of |Terminal| commands I used in order to achieve this::
|
||||
$ scp early-hack-template.script harakiri-sushi
|
||||
$ connect n00dles
|
||||
$ run NUKE.exe
|
||||
$ run early-hack-template.script -t 6
|
||||
$ run early-hack-template.script -t 1
|
||||
$ home
|
||||
$ connect sigma-cosmetics
|
||||
$ run NUKE.exe
|
||||
|
@ -3,7 +3,8 @@
|
||||
Netscript Basic Functions
|
||||
=========================
|
||||
|
||||
This page contains the complete documentation for all functions that are available in Netscript.
|
||||
This page contains a subset of functions that are available in Bitburner.
|
||||
For the complete list see https://github.com/danielyxie/bitburner/tree/dev/markdown
|
||||
This includes information such as function signatures, what they do, and their return values.
|
||||
|
||||
.. toctree::
|
||||
|
@ -1,4 +1,5 @@
|
||||
const { app, BrowserWindow, Menu, shell } = require("electron");
|
||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
const { app, BrowserWindow, Menu, shell } = require("electron");
|
||||
const greenworks = require("./greenworks");
|
||||
|
||||
if (greenworks.init()) {
|
||||
@ -7,14 +8,13 @@ if (greenworks.init()) {
|
||||
console.log("Steam API has failed to initialize.");
|
||||
}
|
||||
|
||||
console.log(greenworks.shutdown);
|
||||
|
||||
const debug = false;
|
||||
|
||||
function createWindow(killall) {
|
||||
const win = new BrowserWindow({
|
||||
show: false,
|
||||
backgroundThrottling: false,
|
||||
backgroundColor: "#000000",
|
||||
});
|
||||
|
||||
win.removeMenu();
|
||||
@ -128,6 +128,7 @@ function setStopProcessHandler(app, window, enabled) {
|
||||
const stopProcessHandler = () => {
|
||||
if (process.platform !== "darwin") {
|
||||
app.quit();
|
||||
// eslint-disable-next-line no-process-exit
|
||||
process.exit(0);
|
||||
}
|
||||
};
|
||||
@ -142,6 +143,6 @@ function setStopProcessHandler(app, window, enabled) {
|
||||
}
|
||||
|
||||
app.whenReady().then(() => {
|
||||
const win = createWindow(false);
|
||||
const win = createWindow(process.argv.includes("--no-scripts"));
|
||||
setStopProcessHandler(app, win, true);
|
||||
});
|
||||
|
@ -12012,6 +12012,34 @@
|
||||
],
|
||||
"name": "getHashUpgradeLevel"
|
||||
},
|
||||
{
|
||||
"kind": "MethodSignature",
|
||||
"canonicalReference": "bitburner!Hacknet#getHashUpgrades:member(1)",
|
||||
"docComment": "/**\n * Get the list of hash upgrades\n *\n * @remarks\n *\n * RAM cost: 0 GB\n *\n * This function is only applicable for Hacknet Servers (the upgraded version of a Hacknet Node).\n *\n * Returns the list of all available hash upgrades that can be used in the spendHashes function.\n *\n * @returns An array containing the available upgrades\n *\n * @example\n * ```ts\n * const upgrades = hacknet.getHashUpgrades(); // [\"Sell for Money\",\"Sell for Corporation Funds\",...]\n * ```\n *\n */\n",
|
||||
"excerptTokens": [
|
||||
{
|
||||
"kind": "Content",
|
||||
"text": "getHashUpgrades(): "
|
||||
},
|
||||
{
|
||||
"kind": "Content",
|
||||
"text": "string[]"
|
||||
},
|
||||
{
|
||||
"kind": "Content",
|
||||
"text": ";"
|
||||
}
|
||||
],
|
||||
"isOptional": false,
|
||||
"returnTypeTokenRange": {
|
||||
"startIndex": 1,
|
||||
"endIndex": 2
|
||||
},
|
||||
"releaseTag": "Public",
|
||||
"overloadIndex": 1,
|
||||
"parameters": [],
|
||||
"name": "getHashUpgrades"
|
||||
},
|
||||
{
|
||||
"kind": "MethodSignature",
|
||||
"canonicalReference": "bitburner!Hacknet#getLevelUpgradeCost:member(1)",
|
||||
|
@ -4,6 +4,8 @@ module.exports = {
|
||||
transform: {
|
||||
"^.+\\.(js|jsx|ts|tsx)$": "babel-jest",
|
||||
},
|
||||
// testMatch: ["**/?(*.)+(test).[jt]s?(x)"],
|
||||
testPathIgnorePatterns: [
|
||||
'.cypress', 'node_modules', 'dist',
|
||||
],
|
||||
testEnvironment: "jsdom",
|
||||
};
|
||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
34
markdown/bitburner.hacknet.gethashupgrades.md
Normal file
34
markdown/bitburner.hacknet.gethashupgrades.md
Normal file
@ -0,0 +1,34 @@
|
||||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [bitburner](./bitburner.md) > [Hacknet](./bitburner.hacknet.md) > [getHashUpgrades](./bitburner.hacknet.gethashupgrades.md)
|
||||
|
||||
## Hacknet.getHashUpgrades() method
|
||||
|
||||
Get the list of hash upgrades
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
getHashUpgrades(): string[];
|
||||
```
|
||||
<b>Returns:</b>
|
||||
|
||||
string\[\]
|
||||
|
||||
An array containing the available upgrades
|
||||
|
||||
## Remarks
|
||||
|
||||
RAM cost: 0 GB
|
||||
|
||||
This function is only applicable for Hacknet Servers (the upgraded version of a Hacknet Node).
|
||||
|
||||
Returns the list of all available hash upgrades that can be used in the spendHashes function.
|
||||
|
||||
## Example
|
||||
|
||||
|
||||
```ts
|
||||
const upgrades = hacknet.getHashUpgrades(); // ["Sell for Money","Sell for Corporation Funds",...]
|
||||
```
|
||||
|
@ -23,6 +23,7 @@ Not all these functions are immediately available.
|
||||
| [getCacheUpgradeCost(index, n)](./bitburner.hacknet.getcacheupgradecost.md) | Calculate the cost of upgrading hacknet node cache. |
|
||||
| [getCoreUpgradeCost(index, n)](./bitburner.hacknet.getcoreupgradecost.md) | Calculate the cost of upgrading hacknet node cores. |
|
||||
| [getHashUpgradeLevel(upgName)](./bitburner.hacknet.gethashupgradelevel.md) | Get the level of a hash upgrade. |
|
||||
| [getHashUpgrades()](./bitburner.hacknet.gethashupgrades.md) | Get the list of hash upgrades |
|
||||
| [getLevelUpgradeCost(index, n)](./bitburner.hacknet.getlevelupgradecost.md) | Calculate the cost of upgrading hacknet node levels. |
|
||||
| [getNodeStats(index)](./bitburner.hacknet.getnodestats.md) | Get the stats of a hacknet node. |
|
||||
| [getPurchaseNodeCost()](./bitburner.hacknet.getpurchasenodecost.md) | Get the price of the next hacknet node. |
|
||||
|
@ -31,9 +31,9 @@ The hostname of the newly purchased server.
|
||||
|
||||
Purchased a server with the specified hostname and amount of RAM.
|
||||
|
||||
The hostname argument can be any data type, but it will be converted to a string and have whitespace removed. Anything that resolves to an empty string will cause the function to fail. If there is already a server with the specified hostname, then the function will automatically append a number at the end of the hostname argument value until it finds a unique hostname. For example, if the script calls `purchaseServer(“foo”, 4)` but a server named “foo” already exists, the it will automatically change the hostname to `foo-0`<!-- -->. If there is already a server with the hostname `foo-0`<!-- -->, then it will change the hostname to `foo-1`<!-- -->, and so on.
|
||||
The hostname argument can be any data type, but it will be converted to a string and have whitespace removed. Anything that resolves to an empty string will cause the function to fail. If there is already a server with the specified hostname, then the function will automatically append a number at the end of the hostname argument value until it finds a unique hostname. For example, if the script calls `purchaseServer(“foo”, 4)` but a server named “foo” already exists, it will automatically change the hostname to `foo-0`<!-- -->. If there is already a server with the hostname `foo-0`<!-- -->, then it will change the hostname to `foo-1`<!-- -->, and so on.
|
||||
|
||||
Note that there is a maximum limit to the amount of servers you can purchase.
|
||||
Note that there is a maximum limit to the number of servers you can purchase.
|
||||
|
||||
Returns the hostname of the newly purchased server as a string. If the function fails to purchase a server, then it will return an empty string. The function will fail if the arguments passed in are invalid, if the player does not have enough money to purchase the specified server, or if the player has exceeded the maximum amount of servers.
|
||||
|
||||
|
208
package-lock.json
generated
208
package-lock.json
generated
@ -1,12 +1,11 @@
|
||||
{
|
||||
"name": "bitburner",
|
||||
"version": "1.1.0",
|
||||
"version": "1.2.0",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "bitburner",
|
||||
"version": "1.1.0",
|
||||
"version": "1.2.0",
|
||||
"hasInstallScript": true,
|
||||
"license": "SEE LICENSE IN license.txt",
|
||||
"dependencies": {
|
||||
@ -22,6 +21,7 @@
|
||||
"@types/escodegen": "^0.0.7",
|
||||
"@types/numeral": "0.0.25",
|
||||
"@types/react": "^17.0.21",
|
||||
"@types/react-beautiful-dnd": "^13.1.2",
|
||||
"@types/react-dom": "^17.0.9",
|
||||
"@types/react-resizable": "^1.7.3",
|
||||
"acorn": "^8.4.1",
|
||||
@ -41,6 +41,7 @@
|
||||
"numeral": "2.0.6",
|
||||
"raw-loader": "^4.0.2",
|
||||
"react": "^17.0.2",
|
||||
"react-beautiful-dnd": "^13.1.0",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-draggable": "^4.4.4",
|
||||
"react-resizable": "^3.0.4",
|
||||
@ -3984,6 +3985,15 @@
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/hoist-non-react-statics": {
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz",
|
||||
"integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==",
|
||||
"dependencies": {
|
||||
"@types/react": "*",
|
||||
"hoist-non-react-statics": "^3.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/istanbul-lib-coverage": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz",
|
||||
@ -4062,6 +4072,14 @@
|
||||
"csstype": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react-beautiful-dnd": {
|
||||
"version": "13.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-beautiful-dnd/-/react-beautiful-dnd-13.1.2.tgz",
|
||||
"integrity": "sha512-+OvPkB8CdE/bGdXKyIhc/Lm2U7UAYCCJgsqmopFmh9gbAudmslkI8eOrPDjg4JhwSE6wytz4a3/wRjKtovHVJg==",
|
||||
"dependencies": {
|
||||
"@types/react": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react-dom": {
|
||||
"version": "17.0.9",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.9.tgz",
|
||||
@ -4078,6 +4096,17 @@
|
||||
"@types/react": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react-redux": {
|
||||
"version": "7.1.20",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.20.tgz",
|
||||
"integrity": "sha512-q42es4c8iIeTgcnB+yJgRTTzftv3eYYvCZOh1Ckn2eX/3o5TdsQYKUWpLoLuGlcY/p+VAhV9IOEZJcWk/vfkXw==",
|
||||
"dependencies": {
|
||||
"@types/hoist-non-react-statics": "^3.3.0",
|
||||
"@types/react": "*",
|
||||
"hoist-non-react-statics": "^3.3.0",
|
||||
"redux": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react-resizable": {
|
||||
"version": "1.7.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-resizable/-/react-resizable-1.7.4.tgz",
|
||||
@ -6659,6 +6688,14 @@
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/css-box-model": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/css-box-model/-/css-box-model-1.2.1.tgz",
|
||||
"integrity": "sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw==",
|
||||
"dependencies": {
|
||||
"tiny-invariant": "^1.0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/css-select": {
|
||||
"version": "4.1.3",
|
||||
"resolved": "https://registry.npmjs.org/css-select/-/css-select-4.1.3.tgz",
|
||||
@ -14566,6 +14603,11 @@
|
||||
"node": ">= 4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/memoize-one": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz",
|
||||
"integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q=="
|
||||
},
|
||||
"node_modules/memory-fs": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz",
|
||||
@ -16303,6 +16345,11 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"node_modules/raf-schd": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/raf-schd/-/raf-schd-4.0.3.tgz",
|
||||
"integrity": "sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ=="
|
||||
},
|
||||
"node_modules/ramda": {
|
||||
"version": "0.27.1",
|
||||
"resolved": "https://registry.npmjs.org/ramda/-/ramda-0.27.1.tgz",
|
||||
@ -16429,6 +16476,24 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-beautiful-dnd": {
|
||||
"version": "13.1.0",
|
||||
"resolved": "https://registry.npmjs.org/react-beautiful-dnd/-/react-beautiful-dnd-13.1.0.tgz",
|
||||
"integrity": "sha512-aGvblPZTJowOWUNiwd6tNfEpgkX5OxmpqxHKNW/4VmvZTNTbeiq7bA3bn5T+QSF2uibXB0D1DmJsb1aC/+3cUA==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.9.2",
|
||||
"css-box-model": "^1.2.0",
|
||||
"memoize-one": "^5.1.1",
|
||||
"raf-schd": "^4.0.2",
|
||||
"react-redux": "^7.2.0",
|
||||
"redux": "^4.0.4",
|
||||
"use-memo-one": "^1.1.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.5 || ^17.0.0",
|
||||
"react-dom": "^16.8.5 || ^17.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-dom": {
|
||||
"version": "17.0.2",
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz",
|
||||
@ -16460,6 +16525,30 @@
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
|
||||
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="
|
||||
},
|
||||
"node_modules/react-redux": {
|
||||
"version": "7.2.6",
|
||||
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.6.tgz",
|
||||
"integrity": "sha512-10RPdsz0UUrRL1NZE0ejTkucnclYSgXp5q+tB5SWx2qeG2ZJQJyymgAhwKy73yiL/13btfB6fPr+rgbMAaZIAQ==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.15.4",
|
||||
"@types/react-redux": "^7.1.20",
|
||||
"hoist-non-react-statics": "^3.3.2",
|
||||
"loose-envify": "^1.4.0",
|
||||
"prop-types": "^15.7.2",
|
||||
"react-is": "^17.0.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.3 || ^17"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"react-dom": {
|
||||
"optional": true
|
||||
},
|
||||
"react-native": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/react-refresh": {
|
||||
"version": "0.10.0",
|
||||
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.10.0.tgz",
|
||||
@ -16625,6 +16714,14 @@
|
||||
"node": ">=8.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/redux": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/redux/-/redux-4.1.2.tgz",
|
||||
"integrity": "sha512-SH8PglcebESbd/shgf6mii6EIoRM0zrQyjcuQ+ojmfxjTtE0z9Y8pa62iA/OJ58qjP6j27uyW4kUF4jl/jd6sw==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.9.2"
|
||||
}
|
||||
},
|
||||
"node_modules/regenerate": {
|
||||
"version": "1.4.2",
|
||||
"resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz",
|
||||
@ -19143,6 +19240,11 @@
|
||||
"resolved": "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz",
|
||||
"integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q="
|
||||
},
|
||||
"node_modules/tiny-invariant": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.2.0.tgz",
|
||||
"integrity": "sha512-1Uhn/aqw5C6RI4KejVeTg6mIS7IqxnLJ8Mv2tV5rTc0qWobay7pDUz6Wi392Cnc8ak1H0F2cjoRzb2/AW4+Fvg=="
|
||||
},
|
||||
"node_modules/tiny-warning": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz",
|
||||
@ -19717,6 +19819,14 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/use-memo-one": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/use-memo-one/-/use-memo-one-1.1.2.tgz",
|
||||
"integrity": "sha512-u2qFKtxLsia/r8qG0ZKkbytbztzRb317XCkT7yP8wxL0tZ/CzK2G+WWie5vWvpyeP7+YoPIwbJoIHJ4Ba4k0oQ==",
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/util": {
|
||||
"version": "0.11.1",
|
||||
"resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz",
|
||||
@ -24597,6 +24707,15 @@
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"@types/hoist-non-react-statics": {
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz",
|
||||
"integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==",
|
||||
"requires": {
|
||||
"@types/react": "*",
|
||||
"hoist-non-react-statics": "^3.3.0"
|
||||
}
|
||||
},
|
||||
"@types/istanbul-lib-coverage": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz",
|
||||
@ -24675,6 +24794,14 @@
|
||||
"csstype": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"@types/react-beautiful-dnd": {
|
||||
"version": "13.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-beautiful-dnd/-/react-beautiful-dnd-13.1.2.tgz",
|
||||
"integrity": "sha512-+OvPkB8CdE/bGdXKyIhc/Lm2U7UAYCCJgsqmopFmh9gbAudmslkI8eOrPDjg4JhwSE6wytz4a3/wRjKtovHVJg==",
|
||||
"requires": {
|
||||
"@types/react": "*"
|
||||
}
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"version": "17.0.9",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.9.tgz",
|
||||
@ -24691,6 +24818,17 @@
|
||||
"@types/react": "*"
|
||||
}
|
||||
},
|
||||
"@types/react-redux": {
|
||||
"version": "7.1.20",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.20.tgz",
|
||||
"integrity": "sha512-q42es4c8iIeTgcnB+yJgRTTzftv3eYYvCZOh1Ckn2eX/3o5TdsQYKUWpLoLuGlcY/p+VAhV9IOEZJcWk/vfkXw==",
|
||||
"requires": {
|
||||
"@types/hoist-non-react-statics": "^3.3.0",
|
||||
"@types/react": "*",
|
||||
"hoist-non-react-statics": "^3.3.0",
|
||||
"redux": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"@types/react-resizable": {
|
||||
"version": "1.7.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-resizable/-/react-resizable-1.7.4.tgz",
|
||||
@ -26762,6 +26900,14 @@
|
||||
"randomfill": "^1.0.3"
|
||||
}
|
||||
},
|
||||
"css-box-model": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/css-box-model/-/css-box-model-1.2.1.tgz",
|
||||
"integrity": "sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw==",
|
||||
"requires": {
|
||||
"tiny-invariant": "^1.0.6"
|
||||
}
|
||||
},
|
||||
"css-select": {
|
||||
"version": "4.1.3",
|
||||
"resolved": "https://registry.npmjs.org/css-select/-/css-select-4.1.3.tgz",
|
||||
@ -32913,6 +33059,11 @@
|
||||
"fs-monkey": "1.0.3"
|
||||
}
|
||||
},
|
||||
"memoize-one": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz",
|
||||
"integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q=="
|
||||
},
|
||||
"memory-fs": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz",
|
||||
@ -34288,6 +34439,11 @@
|
||||
"integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
|
||||
"dev": true
|
||||
},
|
||||
"raf-schd": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/raf-schd/-/raf-schd-4.0.3.tgz",
|
||||
"integrity": "sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ=="
|
||||
},
|
||||
"ramda": {
|
||||
"version": "0.27.1",
|
||||
"resolved": "https://registry.npmjs.org/ramda/-/ramda-0.27.1.tgz",
|
||||
@ -34384,6 +34540,20 @@
|
||||
"object-assign": "^4.1.1"
|
||||
}
|
||||
},
|
||||
"react-beautiful-dnd": {
|
||||
"version": "13.1.0",
|
||||
"resolved": "https://registry.npmjs.org/react-beautiful-dnd/-/react-beautiful-dnd-13.1.0.tgz",
|
||||
"integrity": "sha512-aGvblPZTJowOWUNiwd6tNfEpgkX5OxmpqxHKNW/4VmvZTNTbeiq7bA3bn5T+QSF2uibXB0D1DmJsb1aC/+3cUA==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.9.2",
|
||||
"css-box-model": "^1.2.0",
|
||||
"memoize-one": "^5.1.1",
|
||||
"raf-schd": "^4.0.2",
|
||||
"react-redux": "^7.2.0",
|
||||
"redux": "^4.0.4",
|
||||
"use-memo-one": "^1.1.1"
|
||||
}
|
||||
},
|
||||
"react-dom": {
|
||||
"version": "17.0.2",
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz",
|
||||
@ -34408,6 +34578,19 @@
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
|
||||
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="
|
||||
},
|
||||
"react-redux": {
|
||||
"version": "7.2.6",
|
||||
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.6.tgz",
|
||||
"integrity": "sha512-10RPdsz0UUrRL1NZE0ejTkucnclYSgXp5q+tB5SWx2qeG2ZJQJyymgAhwKy73yiL/13btfB6fPr+rgbMAaZIAQ==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.15.4",
|
||||
"@types/react-redux": "^7.1.20",
|
||||
"hoist-non-react-statics": "^3.3.2",
|
||||
"loose-envify": "^1.4.0",
|
||||
"prop-types": "^15.7.2",
|
||||
"react-is": "^17.0.2"
|
||||
}
|
||||
},
|
||||
"react-refresh": {
|
||||
"version": "0.10.0",
|
||||
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.10.0.tgz",
|
||||
@ -34537,6 +34720,14 @@
|
||||
"picomatch": "^2.2.1"
|
||||
}
|
||||
},
|
||||
"redux": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/redux/-/redux-4.1.2.tgz",
|
||||
"integrity": "sha512-SH8PglcebESbd/shgf6mii6EIoRM0zrQyjcuQ+ojmfxjTtE0z9Y8pa62iA/OJ58qjP6j27uyW4kUF4jl/jd6sw==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.9.2"
|
||||
}
|
||||
},
|
||||
"regenerate": {
|
||||
"version": "1.4.2",
|
||||
"resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz",
|
||||
@ -36569,6 +36760,11 @@
|
||||
"resolved": "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz",
|
||||
"integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q="
|
||||
},
|
||||
"tiny-invariant": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.2.0.tgz",
|
||||
"integrity": "sha512-1Uhn/aqw5C6RI4KejVeTg6mIS7IqxnLJ8Mv2tV5rTc0qWobay7pDUz6Wi392Cnc8ak1H0F2cjoRzb2/AW4+Fvg=="
|
||||
},
|
||||
"tiny-warning": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz",
|
||||
@ -37025,6 +37221,12 @@
|
||||
"resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz",
|
||||
"integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ=="
|
||||
},
|
||||
"use-memo-one": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/use-memo-one/-/use-memo-one-1.1.2.tgz",
|
||||
"integrity": "sha512-u2qFKtxLsia/r8qG0ZKkbytbztzRb317XCkT7yP8wxL0tZ/CzK2G+WWie5vWvpyeP7+YoPIwbJoIHJ4Ba4k0oQ==",
|
||||
"requires": {}
|
||||
},
|
||||
"util": {
|
||||
"version": "0.11.1",
|
||||
"resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz",
|
||||
|
@ -1,10 +1,10 @@
|
||||
{
|
||||
"name": "bitburner",
|
||||
"license": "SEE LICENSE IN license.txt",
|
||||
"version": "1.1.0",
|
||||
"version": "1.2.0",
|
||||
"main": "electron-main.js",
|
||||
"author": {
|
||||
"name": "Daniel Xie"
|
||||
"name": "Daniel Xie & Olivier Gagnon"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/danielyxie/bitburner/issues"
|
||||
@ -22,6 +22,7 @@
|
||||
"@types/escodegen": "^0.0.7",
|
||||
"@types/numeral": "0.0.25",
|
||||
"@types/react": "^17.0.21",
|
||||
"@types/react-beautiful-dnd": "^13.1.2",
|
||||
"@types/react-dom": "^17.0.9",
|
||||
"@types/react-resizable": "^1.7.3",
|
||||
"acorn": "^8.4.1",
|
||||
@ -41,6 +42,7 @@
|
||||
"numeral": "2.0.6",
|
||||
"raw-loader": "^4.0.2",
|
||||
"react": "^17.0.2",
|
||||
"react-beautiful-dnd": "^13.1.0",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-draggable": "^4.4.4",
|
||||
"react-resizable": "^3.0.4",
|
||||
@ -104,11 +106,13 @@
|
||||
"build": "webpack --mode production",
|
||||
"build:dev": "webpack --mode development",
|
||||
"lint": "eslint --fix . --ext js,jsx,ts,tsx",
|
||||
"lint:report": "eslint --ext js,jsx,ts,tsx .",
|
||||
"preinstall": "node ./scripts/engines-check.js",
|
||||
"test": "jest",
|
||||
"test:watch": "jest --watch",
|
||||
"watch": "webpack --watch --mode production",
|
||||
"watch:dev": "webpack --watch --mode development",
|
||||
"package-electron": "electron-packager .package bitburner --all --out .build --overwrite --icon .package/icon.png",
|
||||
"electron": "cp -r electron/* .package && cp index.html .package && cp main.bundle.js .package && cp dist/vendor.bundle.js .package/dist/ && cp -r dist/ext .package/dist/ && electron-packager .package bitburner --all --out .build --overwrite --icon .package/icon.png",
|
||||
"allbuild": "npm run build && npm run electron && git add --all && git commit --amend --no-edit && git push -f -u origin dev"
|
||||
}
|
||||
|
@ -36,7 +36,7 @@ export function printAliases(): void {
|
||||
|
||||
// Returns true if successful, false otherwise
|
||||
export function parseAliasDeclaration(dec: string, global = false): boolean {
|
||||
const re = /^([_|\w|!|%|,|@]+)="(.+)"$/;
|
||||
const re = /^([\w|!|%|,|@|-]+)="(.+)"$/;
|
||||
const matches = dec.match(re);
|
||||
if (matches == null || matches.length != 3) {
|
||||
return false;
|
||||
|
@ -1844,7 +1844,7 @@ function initAugmentations(): void {
|
||||
moneyCost: 3.75e8,
|
||||
info:
|
||||
"A drug that genetically modifies the neurons in the brain " +
|
||||
"resulting in neurons never die, continuously " +
|
||||
"resulting in neurons that never die, continuously " +
|
||||
"regenerate, and strengthen themselves.",
|
||||
hacking_exp_mult: 1.4,
|
||||
});
|
||||
|
@ -16,6 +16,8 @@ import Typography from "@mui/material/Typography";
|
||||
import Button from "@mui/material/Button";
|
||||
import Tooltip from "@mui/material/Tooltip";
|
||||
import Box from "@mui/material/Box";
|
||||
import { Settings } from "../../Settings/Settings";
|
||||
import { ConfirmationModal } from "../../ui/React/ConfirmationModal";
|
||||
|
||||
interface IProps {
|
||||
exportGameFn: () => void;
|
||||
@ -23,6 +25,7 @@ interface IProps {
|
||||
}
|
||||
|
||||
export function AugmentationsRoot(props: IProps): React.ReactElement {
|
||||
const [installOpen, setInstallOpen] = useState(false);
|
||||
const player = use.Player();
|
||||
const setRerender = useState(false)[1];
|
||||
function rerender(): void {
|
||||
@ -43,6 +46,14 @@ export function AugmentationsRoot(props: IProps): React.ReactElement {
|
||||
return "";
|
||||
}
|
||||
|
||||
function doInstall(): void {
|
||||
if (!Settings.SuppressBuyAugmentationConfirmation) {
|
||||
setInstallOpen(true);
|
||||
} else {
|
||||
props.installAugmentationsFn();
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Typography variant="h4">Augmentations</Typography>
|
||||
@ -73,11 +84,36 @@ export function AugmentationsRoot(props: IProps): React.ReactElement {
|
||||
<Box mx={2}>
|
||||
<Tooltip title={<Typography>'I never asked for this'</Typography>}>
|
||||
<span>
|
||||
<Button disabled={player.queuedAugmentations.length === 0} onClick={props.installAugmentationsFn}>
|
||||
<Button disabled={player.queuedAugmentations.length === 0} onClick={doInstall}>
|
||||
Install Augmentations
|
||||
</Button>
|
||||
</span>
|
||||
</Tooltip>
|
||||
<ConfirmationModal
|
||||
open={installOpen}
|
||||
onClose={() => setInstallOpen(false)}
|
||||
onConfirm={props.installAugmentationsFn}
|
||||
confirmationText={
|
||||
<>
|
||||
Installing will reset
|
||||
<br />
|
||||
<br />- money
|
||||
<br />- skill / experience
|
||||
<br />- every server except home
|
||||
<br />- factions and reputation
|
||||
<br />
|
||||
<br />
|
||||
You will keep:
|
||||
<br />
|
||||
<br />- All scripts on home
|
||||
<br />- home ram and cores
|
||||
<br />
|
||||
<br />
|
||||
It is recommended to install several Augmentations at once. Preferably everything from any faction of your
|
||||
chosing.
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<Tooltip title={<Typography>It's always a good idea to backup/export your save!</Typography>}>
|
||||
<Button sx={{ mx: 2 }} onClick={doExport} color="error">
|
||||
Backup Save {exportBonusStr()}
|
||||
|
@ -29,7 +29,6 @@ export function PurchasedAugmentations(): React.ReactElement {
|
||||
if (ownedAug.name === AugmentationNames.NeuroFluxGovernor) {
|
||||
level = ownedAug.level;
|
||||
}
|
||||
|
||||
augs.push(<AugmentationAccordion key={aug.name} aug={aug} level={level} />);
|
||||
}
|
||||
|
||||
|
143
src/Constants.ts
143
src/Constants.ts
@ -111,8 +111,8 @@ export const CONSTANTS: {
|
||||
TotalNumBitNodes: number;
|
||||
LatestUpdate: string;
|
||||
} = {
|
||||
VersionString: "1.1.0",
|
||||
VersionNumber: 6,
|
||||
VersionString: "1.2.0",
|
||||
VersionNumber: 7,
|
||||
|
||||
// Speed (in ms) at which the main loop is updated
|
||||
_idleSpeed: 200,
|
||||
@ -273,72 +273,103 @@ export const CONSTANTS: {
|
||||
TotalNumBitNodes: 24,
|
||||
|
||||
LatestUpdate: `
|
||||
v1.1.0 - 2021-12-03 BN13: They're Lunatics (hydroflame & community)
|
||||
-------------------------------------------------------
|
||||
v1.1.0 - 2021-12-18 You guys are awesome (community because they're god damn awesome)
|
||||
-------------------------------------------------------------------------------------
|
||||
|
||||
** BN13: They're Lunatics **
|
||||
** Script Editor **
|
||||
|
||||
* BN13 added.
|
||||
* The text editor can open several files at once. (@Rez855 / @Shadow72)
|
||||
It's not perfect so keep the feedback coming.
|
||||
|
||||
** Steam **
|
||||
|
||||
* Tested on all 3 major OS.
|
||||
* 94 achievements added
|
||||
* Release is 2021-12-10.
|
||||
* Windows has a new launch option that lets player start with killing all their scripts
|
||||
This is a safety net in case all the other safety nets fail.
|
||||
* Linux has several launch options that use different flags for different OS.
|
||||
* Debug and Fullscreen are available in the window utility bar.
|
||||
* Tried (and maybe failed) to make the game completely kill itself after closing.
|
||||
This one I still don't know wtf is going.
|
||||
* No longer has background throttling.
|
||||
* Default color should be pitch black when loading
|
||||
* Add BN13: Challenge achievement.
|
||||
|
||||
** Corporation API **
|
||||
** Tutorial **
|
||||
|
||||
* Added corporation API. (Unstable)
|
||||
* I watched someone play bitburner on youtube and reworked part of
|
||||
the tutorial to try to make some parts of the game clearer.
|
||||
https://www.youtube.com/watch?v=-_JETXff4Zo
|
||||
* Add option to restart tutorial.
|
||||
|
||||
** Netscript **
|
||||
|
||||
* tprintf crashes when not giving a format as first arg.
|
||||
* tprintf no longer prints filename (@BartKoppelmans)
|
||||
* TIX buy/sell/sellShort all return askprice/bidprice (@Insight)
|
||||
* getRunningScript now works.
|
||||
* Fix disableLog for gang and TIX API
|
||||
* getOwnedSourceFiles is not singularity anymore (makes it easier to share scripts.) (@theit8514)
|
||||
* true/false is a valid value to send to other scripts.
|
||||
* workForFaction no longer returns null when trying to work for gang.
|
||||
* Scripts logging no longer generates the string if logging is disabled.
|
||||
This should give performance boost for some scripts.
|
||||
|
||||
** Gang **
|
||||
|
||||
* Gang with 0 territory can no longer fight
|
||||
* Territory now caps at exactly 0 or 1.
|
||||
* getGangInformation returns more information.
|
||||
* getAscensionResult added
|
||||
* getMemberInformation returns more info
|
||||
* Formulas API has new functions for gang.
|
||||
* Added documentation for corp API.
|
||||
* exec has clearer error message when you send invalid data.
|
||||
* getServer returns all defined field for hacknet servers.
|
||||
* Fix a bug with scp multiple files (@theit8514)
|
||||
* Stack traces should be smarter at replacing blobs with filenames
|
||||
* Fix a weird error message that would occur when throwing raw strings.
|
||||
* Fix shortcuts not working.
|
||||
* Re-added setFocus and isFocused (@theit8514)
|
||||
* new function getHashUpgrades (@MartinFournier)
|
||||
* enableLog accepts "ALL" like disableLog (@wynro)
|
||||
* toast() doesn't crash on invalid data (@ivanjermakov)
|
||||
* alert() doesn't crash on invalid data (@Siern)
|
||||
* Fixed an issue where scripts don't run where they should.
|
||||
* Sleeve getInformation now returns cha
|
||||
* getServer does work with no argument now
|
||||
* workForFaction returns false when it mistakenly returned null
|
||||
|
||||
** Character Overview **
|
||||
|
||||
* The character overview now shows the amount of exp needed to next level (@MartinFournier)
|
||||
|
||||
** Misc. **
|
||||
|
||||
* Clicking "previous" on the browser will not pretend you had unsaved information
|
||||
allowing you to cancel if needs be.
|
||||
* Fixed some tail box coloring issue.
|
||||
* Fixed BladeBurner getCityCommunities ram cost
|
||||
* The download terminal command no longer duplicate extensions (@Insight)
|
||||
* Fix #000 on #000 text in blackjack. (@Insight)
|
||||
* Remove reference to .fconf
|
||||
* Tail boxes all die on soft reset.
|
||||
* Fix codign contract focus bug.
|
||||
* Megacorp factions simply re-invite you instead of auto added on reset. (@theit8514)
|
||||
* Tail window is bound to html body.
|
||||
* Infiltration reward is tied to your potential stats, not your actual stats
|
||||
So you won't lose reward for doing the same thing over and over.
|
||||
* intelligence lowers program creation requirements.
|
||||
* Terminal parses true as the boolean, not the string.
|
||||
* Tail and kill autocomplete using the ns2 autocomplete feature.
|
||||
* scan-analyze doesn't take up as many terminal entries.
|
||||
* GangOtherInfo documentation now renders correctly.
|
||||
* ActiveScripts search box also searches for script names.
|
||||
* Infinite money no longer allows for infinite hacknet server.
|
||||
* Blackjack doesn't make you lose money twice.
|
||||
* Recent Scripts is now from most to least recent.
|
||||
* Fix mathjax ascii art bug in NiteSec.
|
||||
* Remove warning that the theme editor is slow, it's only slow in dev mode.
|
||||
* In BN8 is it possible to reduce the money on a server without gaining any.
|
||||
* In the options, the timestamp feature has a placeholder explaining the expected format.
|
||||
* Bunch of doc typo fix. (hydroflame & @BartKoppelmans & @cvr-119)
|
||||
* World Daemon difficulty is increased depending on BitNode.
|
||||
* hacking skill nerf was loosened in some BNs.
|
||||
* nerf noodle bar
|
||||
* Add option to supress Game Saved! toasts (@MartinFournier)
|
||||
* Fix bug where ctrl+alt+j was eaten by the wrong process. (@billyvg)
|
||||
* Theme Editor lets you paste colors (@MartinFournier)
|
||||
* ctrl + u/k/w should work on terminal (@billyvg)
|
||||
* Game now shows commit number, this is mostly for me. (@MartinFourier)
|
||||
* running a bad script will give a clearer error message (@TheCoderJT)
|
||||
* Default terminal capacity is maximum (@SayntGarmo)
|
||||
* Fix problems with cp and mv (@theit8514)
|
||||
* Make monaco load fully offline for players behind firewalls.
|
||||
* change beginer guide to use n00dles instead of foodnstuff
|
||||
* BN13 is harder
|
||||
* nerf int gain from manualHack
|
||||
* Fix UI displaying wrong stats (@DJMatch3000)
|
||||
* Fix button not disabling as it should.
|
||||
* New location in Ishima.
|
||||
* Add setting to suppress stock market popups.
|
||||
* Typo fixes (@Hedrauta, @cvr-119, @Ationi, @millennIumAMbiguity
|
||||
@TealKoi, @TheCoderJT, @cblte, @2PacIsAlive, @MageKing17,
|
||||
@Xynrati, @Adraxas, @pobiega)
|
||||
* Fix 100% territory achievement.
|
||||
* Reword message on active scripts page.
|
||||
* Fix terminal not clearing after BN
|
||||
* Remove references to .fconf
|
||||
* Augmentation pages shows BN difficulty with SF5
|
||||
* Fix scripts saving on wrong server while 'connect'ing
|
||||
* Fix gym discount not working.
|
||||
* Fix scan-analyze not working with timestamps
|
||||
* Hash upgrades remember last choice.
|
||||
* Save files now sort by date
|
||||
* The covenant no longer supports negative memory purchases
|
||||
* Fix corp shares buyback triggering by pressing enter
|
||||
* Staneks gift display avg / num charges
|
||||
* Infiltration rewards no longer decay with better stats
|
||||
* terminal 'true' is parsed as boolean not string
|
||||
* tail and kill use autocomplete()
|
||||
* Fix focus for coding contract
|
||||
* massive boost to noodle bar.
|
||||
|
||||
** Special Thanks **
|
||||
|
||||
* Special thank you to everyone on Discord who can answer
|
||||
new player questions so I can focus on more important things.
|
||||
`,
|
||||
};
|
||||
|
@ -5,6 +5,7 @@ import { Player } from "../Player";
|
||||
import { Terminal } from "../Terminal";
|
||||
import { SpecialServers } from "../Server/data/SpecialServers";
|
||||
import { Money } from "../ui/React/Money";
|
||||
import { DarkWebItem } from "./DarkWebItem";
|
||||
|
||||
//Posts a "help" message if connected to DarkWeb
|
||||
export function checkIfConnectedToDarkweb(): void {
|
||||
@ -33,7 +34,8 @@ export function buyDarkwebItem(itemName: string): void {
|
||||
itemName = itemName.toLowerCase();
|
||||
|
||||
// find the program that matches, if any
|
||||
let item = null;
|
||||
let item: DarkWebItem | null = null;
|
||||
|
||||
for (const key in DarkWebItems) {
|
||||
const i = DarkWebItems[key];
|
||||
if (i.program.toLowerCase() == itemName) {
|
||||
@ -61,7 +63,19 @@ export function buyDarkwebItem(itemName: string): void {
|
||||
|
||||
// buy and push
|
||||
Player.loseMoney(item.price, "other");
|
||||
|
||||
const programsRef = Player.getHomeComputer().programs;
|
||||
// Remove partially created program if there is one
|
||||
const existingPartialExeIndex = programsRef.findIndex(
|
||||
(program) => item?.program && program.startsWith(item?.program),
|
||||
);
|
||||
// findIndex returns -1 if there is no match, we only want to splice on a match
|
||||
if (existingPartialExeIndex > -1) {
|
||||
programsRef.splice(existingPartialExeIndex, 1);
|
||||
}
|
||||
// Add the newly bought, full .exe
|
||||
Player.getHomeComputer().programs.push(item.program);
|
||||
|
||||
Terminal.print(
|
||||
"You have purchased the " + item.program + " program. The new program can be found on your home computer.",
|
||||
);
|
||||
|
@ -15,6 +15,6 @@ export const DarkWebItems: IMap<DarkWebItem> = {
|
||||
),
|
||||
DeepscanV1: new DarkWebItem(Programs.DeepscanV1.name, 500000, "Enables 'scan-analyze' with a depth up to 5."),
|
||||
DeepscanV2: new DarkWebItem(Programs.DeepscanV2.name, 25e6, "Enables 'scan-analyze' with a depth up to 10."),
|
||||
AutolinkProgram: new DarkWebItem(Programs.AutoLink.name, 1e6, "Enables direct connect via 'scan-analyze."),
|
||||
AutolinkProgram: new DarkWebItem(Programs.AutoLink.name, 1e6, "Enables direct connect via 'scan-analyze'."),
|
||||
FormulasProgram: new DarkWebItem(Programs.Formulas.name, 5e9, "Unlock access to the formulas API."),
|
||||
};
|
||||
|
@ -32,6 +32,7 @@ function bitNodeFinishedState(): boolean {
|
||||
return Player.bladeburner !== null && Player.bladeburner.blackops.hasOwnProperty("Operation Daedalus");
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
function sfAchievement(): Achievement[] {
|
||||
const achs: Achievement[] = [];
|
||||
for (let i = 0; i <= 11; i++) {
|
||||
@ -189,7 +190,7 @@ const achievements: Achievement[] = [
|
||||
},
|
||||
{
|
||||
ID: "GANG_TERRITORY",
|
||||
Condition: () => Player.gang !== null && AllGangs[Player.gang.facName].territory === 1,
|
||||
Condition: () => Player.gang !== null && AllGangs[Player.gang.facName].territory >= 0.999,
|
||||
},
|
||||
{
|
||||
ID: "GANG_MEMBER_POWER",
|
||||
|
@ -24,9 +24,9 @@ function timeCompression(): void {
|
||||
return;
|
||||
}
|
||||
last = now;
|
||||
window.setTimeout(minute, 1000);
|
||||
window.setTimeout(minute, 1000 * 60);
|
||||
}
|
||||
window.setTimeout(minute, 1000);
|
||||
window.setTimeout(minute, 1000 * 60);
|
||||
}
|
||||
|
||||
export function startExploits(): void {
|
||||
|
@ -304,7 +304,10 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
|
||||
const checkSingularityAccess = function (func: any, n: any): void {
|
||||
if (Player.bitNodeN !== 4) {
|
||||
if (SourceFileFlags[4] < n) {
|
||||
throw makeRuntimeErrorMsg(func, `This singularity function requires Source-File 4-${n} to run.`);
|
||||
throw makeRuntimeErrorMsg(
|
||||
func,
|
||||
`This singularity function requires Source-File 4-${n} to run. A power up you obtain later in the game. It will be very obvious when and how you can obtain it.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -754,7 +757,12 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
|
||||
}
|
||||
},
|
||||
enableLog: function (fn: any): any {
|
||||
if (possibleLogs[fn] === undefined) {
|
||||
if (fn === "ALL") {
|
||||
for (fn in possibleLogs) {
|
||||
delete workerScript.disableLogs[fn];
|
||||
}
|
||||
workerScript.log("enableLog", () => `Enabled logging for all functions`);
|
||||
} else if (possibleLogs[fn] === undefined) {
|
||||
throw makeRuntimeErrorMsg("enableLog", `Invalid argument: ${fn}.`);
|
||||
}
|
||||
delete workerScript.disableLogs[fn];
|
||||
@ -1606,7 +1614,7 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
|
||||
|
||||
const cost = getPurchaseServerCost(ram);
|
||||
if (cost === Infinity) {
|
||||
workerScript.log("purchaseServer", () => `Invalid argument: ram='${ram}'`);
|
||||
workerScript.log("purchaseServer", () => `Invalid argument: ram='${ram}' must be power of 2`);
|
||||
return "";
|
||||
}
|
||||
|
||||
@ -2123,15 +2131,15 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
|
||||
return Player.playtimeSinceLastAug;
|
||||
},
|
||||
alert: function (message: any): void {
|
||||
message = toNative(message);
|
||||
dialogBoxCreate(JSON.stringify(message));
|
||||
message = argsToString([message]);
|
||||
dialogBoxCreate(message);
|
||||
},
|
||||
toast: function (message: any, variant: any = "success"): void {
|
||||
if (!["success", "info", "warning", "error"].includes(variant))
|
||||
throw new Error(`variant must be one of "success", "info", "warning", or "error"`);
|
||||
|
||||
message = toNative(message);
|
||||
SnackbarEvents.emit(JSON.stringify(message), variant);
|
||||
message = argsToString([message]);
|
||||
SnackbarEvents.emit(message, variant);
|
||||
},
|
||||
prompt: function (txt: any): any {
|
||||
if (!isString(txt)) {
|
||||
|
@ -16,6 +16,8 @@ import {
|
||||
} from "../Hacknet/HacknetHelpers";
|
||||
import { HacknetServer } from "../Hacknet/HacknetServer";
|
||||
import { HacknetNode } from "../Hacknet/HacknetNode";
|
||||
import { HashUpgrades } from "../Hacknet/HashUpgrades";
|
||||
import { HashUpgrade } from "../Hacknet/HashUpgrade";
|
||||
import { GetServer } from "../Server/AllServers";
|
||||
|
||||
import { Hacknet as IHacknet, NodeStats } from "../ScriptEditor/NetscriptDefinitions";
|
||||
@ -166,6 +168,12 @@ export function NetscriptHacknet(player: IPlayer, workerScript: WorkerScript, he
|
||||
}
|
||||
return purchaseHashUpgrade(player, upgName, upgTarget);
|
||||
},
|
||||
getHashUpgrades: function(): string[] {
|
||||
if (!hasHacknetServers(player)) {
|
||||
return [];
|
||||
}
|
||||
return Object.values(HashUpgrades).map((upgrade: HashUpgrade) => upgrade.name);
|
||||
},
|
||||
getHashUpgradeLevel: function (upgName: any): number {
|
||||
const level = player.hashManager.upgrades[upgName];
|
||||
if (level === undefined) {
|
||||
|
@ -4,6 +4,7 @@ const defaultInterpreter = new Interpreter("", () => undefined);
|
||||
|
||||
// the acorn interpreter has a bug where it doesn't convert arrays correctly.
|
||||
// so we have to more or less copy it here.
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
export function toNative(pseudoObj: any): any {
|
||||
if (pseudoObj == null) return null;
|
||||
if (
|
||||
|
@ -29,6 +29,7 @@ import { ICodingContractReward } from "../CodingContracts";
|
||||
import { IRouter } from "../ui/Router";
|
||||
import { WorkerScript } from "../Netscript/WorkerScript";
|
||||
import { HacknetServer } from "../Hacknet/HacknetServer";
|
||||
import { ISkillProgress } from "./formulas/skill";
|
||||
|
||||
export interface IPlayer {
|
||||
// Class members
|
||||
@ -241,6 +242,7 @@ export interface IPlayer {
|
||||
getIntelligenceBonus(weight: number): number;
|
||||
getCasinoWinnings(): number;
|
||||
quitJob(company: string): void;
|
||||
hasJob(): boolean;
|
||||
createHacknetServer(): HacknetServer;
|
||||
startCreateProgramWork(router: IRouter, programName: string, time: number, reqLevel: number): void;
|
||||
queueAugmentation(augmentationName: string): void;
|
||||
@ -259,6 +261,7 @@ export interface IPlayer {
|
||||
prestigeAugmentation(): void;
|
||||
prestigeSourceFile(): void;
|
||||
calculateSkill(exp: number, mult?: number): number;
|
||||
calculateSkillProgress(exp: number, mult?: number): ISkillProgress;
|
||||
resetWorkStatus(generalType?: string, group?: string, workType?: string): void;
|
||||
getWorkHackExpGain(): number;
|
||||
getWorkStrExpGain(): number;
|
||||
|
@ -34,6 +34,7 @@ import { CityName } from "../../Locations/data/CityNames";
|
||||
|
||||
import { MoneySourceTracker } from "../../utils/MoneySourceTracker";
|
||||
import { Reviver, Generic_toJSON, Generic_fromJSON } from "../../utils/JSONReviver";
|
||||
import { ISkillProgress } from "../formulas/skill";
|
||||
|
||||
export class PlayerObject implements IPlayer {
|
||||
// Class members
|
||||
@ -246,6 +247,7 @@ export class PlayerObject implements IPlayer {
|
||||
getIntelligenceBonus: (weight: number) => number;
|
||||
getCasinoWinnings: () => number;
|
||||
quitJob: (company: string) => void;
|
||||
hasJob: () => boolean;
|
||||
process: (router: IRouter, numCycles?: number) => void;
|
||||
createHacknetServer: () => HacknetServer;
|
||||
startCreateProgramWork: (router: IRouter, programName: string, time: number, reqLevel: number) => void;
|
||||
@ -265,6 +267,7 @@ export class PlayerObject implements IPlayer {
|
||||
prestigeAugmentation: () => void;
|
||||
prestigeSourceFile: () => void;
|
||||
calculateSkill: (exp: number, mult?: number) => number;
|
||||
calculateSkillProgress: (exp: number, mult?: number) => ISkillProgress;
|
||||
resetWorkStatus: (generalType?: string, group?: string, workType?: string) => void;
|
||||
getWorkHackExpGain: () => number;
|
||||
getWorkStrExpGain: () => number;
|
||||
@ -470,6 +473,7 @@ export class PlayerObject implements IPlayer {
|
||||
this.prestigeSourceFile = generalMethods.prestigeSourceFile;
|
||||
this.receiveInvite = generalMethods.receiveInvite;
|
||||
this.calculateSkill = generalMethods.calculateSkill;
|
||||
this.calculateSkillProgress = generalMethods.calculateSkillProgress;
|
||||
this.updateSkillLevels = generalMethods.updateSkillLevels;
|
||||
this.resetMultipliers = generalMethods.resetMultipliers;
|
||||
this.hasProgram = generalMethods.hasProgram;
|
||||
@ -528,6 +532,7 @@ export class PlayerObject implements IPlayer {
|
||||
this.applyForJob = generalMethods.applyForJob;
|
||||
this.getNextCompanyPosition = generalMethods.getNextCompanyPosition;
|
||||
this.quitJob = generalMethods.quitJob;
|
||||
this.hasJob = generalMethods.hasJob;
|
||||
this.applyForSoftwareJob = generalMethods.applyForSoftwareJob;
|
||||
this.applyForSoftwareConsultantJob = generalMethods.applyForSoftwareConsultantJob;
|
||||
this.applyForItJob = generalMethods.applyForItJob;
|
||||
|
@ -26,7 +26,7 @@ import { Locations } from "../../Locations/Locations";
|
||||
import { CityName } from "../../Locations/data/CityNames";
|
||||
import { LocationName } from "../../Locations/data/LocationNames";
|
||||
import { Sleeve } from "../../PersonObjects/Sleeve/Sleeve";
|
||||
import { calculateSkill as calculateSkillF } from "../formulas/skill";
|
||||
import { calculateSkill as calculateSkillF, calculateSkillProgress as calculateSkillProgressF, ISkillProgress } from "../formulas/skill";
|
||||
import { calculateIntelligenceBonus } from "../formulas/intelligence";
|
||||
import {
|
||||
getHackingWorkRepGain,
|
||||
@ -226,6 +226,11 @@ export function calculateSkill(this: IPlayer, exp: number, mult = 1): number {
|
||||
return calculateSkillF(exp, mult);
|
||||
}
|
||||
|
||||
//Calculates skill level progress based on experience. The same formula will be used for every skill
|
||||
export function calculateSkillProgress(this: IPlayer, exp: number, mult = 1): ISkillProgress {
|
||||
return calculateSkillProgressF(exp, mult);
|
||||
}
|
||||
|
||||
export function updateSkillLevels(this: IPlayer): void {
|
||||
this.hacking = Math.max(
|
||||
1,
|
||||
@ -1777,6 +1782,15 @@ export function quitJob(this: IPlayer, company: string): void {
|
||||
delete this.jobs[company];
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to see if the player has at least one job assigned to them
|
||||
* @param this The player instance
|
||||
* @returns Whether the user has at least one job
|
||||
*/
|
||||
export function hasJob(this: IPlayer): boolean {
|
||||
return Boolean(Object.keys(this.jobs).length);
|
||||
}
|
||||
|
||||
export function applyForSoftwareJob(this: IPlayer, sing = false): boolean {
|
||||
return this.applyForJob(CompanyPositions[posNames.SoftwareCompanyPositions[0]], sing);
|
||||
}
|
||||
|
@ -5,3 +5,42 @@ export function calculateSkill(exp: number, mult = 1): number {
|
||||
export function calculateExp(skill: number, mult = 1): number {
|
||||
return Math.exp((skill / mult + 200) / 32) - 534.6;
|
||||
}
|
||||
|
||||
export function calculateSkillProgress(exp: number, mult = 1): ISkillProgress {
|
||||
const currentSkill = calculateSkill(exp, mult);
|
||||
const nextSkill = currentSkill + 1;
|
||||
|
||||
let baseExperience = calculateExp(currentSkill, mult);
|
||||
if (baseExperience < 0) baseExperience = 0;
|
||||
|
||||
let nextExperience = calculateExp(nextSkill, mult)
|
||||
if (nextExperience < 0) nextExperience = 0;
|
||||
|
||||
const normalize = (value: number): number => ((value - baseExperience) * 100) / (nextExperience - baseExperience);
|
||||
|
||||
return {
|
||||
currentSkill,
|
||||
nextSkill,
|
||||
baseExperience,
|
||||
experience: exp,
|
||||
nextExperience,
|
||||
progress: (nextExperience - baseExperience !== 0) ? normalize(exp) : 99.99
|
||||
}
|
||||
}
|
||||
|
||||
export interface ISkillProgress {
|
||||
currentSkill: number;
|
||||
nextSkill: number;
|
||||
baseExperience: number;
|
||||
experience: number;
|
||||
nextExperience: number;
|
||||
progress: number;
|
||||
}
|
||||
|
||||
export function getEmptySkillProgress(): ISkillProgress {
|
||||
return {
|
||||
currentSkill: 0, nextSkill: 0,
|
||||
baseExperience: 0, experience: 0, nextExperience: 0,
|
||||
progress: 0,
|
||||
};
|
||||
}
|
||||
|
@ -67,7 +67,11 @@ class BitburnerSaveObject {
|
||||
const saveString = this.getSaveString();
|
||||
|
||||
save(saveString)
|
||||
.then(() => SnackbarEvents.emit("Game Saved!", "info"))
|
||||
.then(() => {
|
||||
if (!Settings.SuppressSavedGameToast) {
|
||||
SnackbarEvents.emit("Game Saved!", "info")
|
||||
}
|
||||
})
|
||||
.catch((err) => console.error(err));
|
||||
}
|
||||
|
||||
|
@ -1,3 +1,5 @@
|
||||
export const validScriptExtensions: Array<string> = [`.js`, `.script`, `.ns`];
|
||||
|
||||
export function isScriptFilename(f: string): boolean {
|
||||
return f.endsWith(".js") || f.endsWith(".script") || f.endsWith(".ns");
|
||||
return validScriptExtensions.some((ext) => f.endsWith(ext));
|
||||
}
|
||||
|
22
src/ScriptEditor/NetscriptDefinitions.d.ts
vendored
22
src/ScriptEditor/NetscriptDefinitions.d.ts
vendored
@ -2290,6 +2290,22 @@ export interface Hacknet {
|
||||
*/
|
||||
spendHashes(upgName: string, upgTarget?: string): boolean;
|
||||
|
||||
/**
|
||||
* Get the list of hash upgrades
|
||||
* @remarks
|
||||
* RAM cost: 0 GB
|
||||
*
|
||||
* This function is only applicable for Hacknet Servers (the upgraded version of a Hacknet Node).
|
||||
*
|
||||
* Returns the list of all available hash upgrades that can be used in the spendHashes function.
|
||||
* @example
|
||||
* ```ts
|
||||
* const upgrades = hacknet.getHashUpgrades(); // ["Sell for Money","Sell for Corporation Funds",...]
|
||||
* ```
|
||||
* @returns An array containing the available upgrades
|
||||
*/
|
||||
getHashUpgrades(): string[];
|
||||
|
||||
/**
|
||||
* Get the level of a hash upgrade.
|
||||
* @remarks
|
||||
@ -4198,7 +4214,7 @@ export interface NS extends Singularity {
|
||||
* @remarks
|
||||
* RAM cost: 0.05 GB
|
||||
*
|
||||
* Runs the NUKE.exe program on the target server. NUKE.exe must exist on your home computer.
|
||||
* Running NUKE.exe on a target server gives you root access which means you can executes scripts on said server. NUKE.exe must exist on your home computer.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
@ -4362,7 +4378,7 @@ export interface NS extends Singularity {
|
||||
* @param args - Additional arguments to pass into the new script that is being run. Note that if any arguments are being passed into the new script, then the third argument numThreads must be filled in with a value.
|
||||
* @returns Returns the PID of a successfully started script, and 0 otherwise.
|
||||
*/
|
||||
exec(script: string, host: string, numThreads?: number, ...args: string[]): number;
|
||||
exec(script: string, host: string, numThreads?: number, ...args: Array<string | number | boolean>): number;
|
||||
|
||||
/**
|
||||
* Terminate current script and start another in 10s.
|
||||
@ -5242,7 +5258,7 @@ export interface NS extends Singularity {
|
||||
* @param format - Formatter.
|
||||
* @returns Formated number.
|
||||
*/
|
||||
nFormat(n: number, format: string): number;
|
||||
nFormat(n: number, format: string): string;
|
||||
|
||||
/**
|
||||
* Format time to readable string
|
||||
|
@ -1,8 +1,10 @@
|
||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||
import React, { useState, useEffect, useRef, useMemo } from "react";
|
||||
import Editor from "@monaco-editor/react";
|
||||
import Editor, { Monaco } from "@monaco-editor/react";
|
||||
import * as monaco from "monaco-editor";
|
||||
|
||||
type IStandaloneCodeEditor = monaco.editor.IStandaloneCodeEditor;
|
||||
type ITextModel = monaco.editor.ITextModel;
|
||||
import { OptionsModal } from "./OptionsModal";
|
||||
import { Options } from "./Options";
|
||||
import { isValidFilePath } from "../../Terminal/DirectoryHelpers";
|
||||
@ -15,7 +17,7 @@ import { TextFile } from "../../TextFile";
|
||||
import { calculateRamUsage, checkInfiniteLoop } from "../../Script/RamCalculations";
|
||||
import { RamCalculationErrorCode } from "../../Script/RamCalculationErrorCodes";
|
||||
import { numeralWrapper } from "../../ui/numeralFormat";
|
||||
import { CursorPositions } from "../CursorPositions";
|
||||
import { DragDropContext, Droppable, Draggable } from 'react-beautiful-dnd';
|
||||
|
||||
import { NetscriptFunctions } from "../../NetscriptFunctions";
|
||||
import { WorkerScript } from "../../Netscript/WorkerScript";
|
||||
@ -30,17 +32,28 @@ import Button from "@mui/material/Button";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import Link from "@mui/material/Link";
|
||||
import Box from "@mui/material/Box";
|
||||
import TextField from "@mui/material/TextField";
|
||||
import IconButton from "@mui/material/IconButton";
|
||||
import SettingsIcon from "@mui/icons-material/Settings";
|
||||
import { PromptEvent } from "../../ui/React/PromptManager";
|
||||
|
||||
import libSource from "!!raw-loader!../NetscriptDefinitions.d.ts";
|
||||
|
||||
|
||||
interface IProps {
|
||||
filename: string;
|
||||
code: string;
|
||||
hostname: string;
|
||||
player: IPlayer;
|
||||
router: IRouter;
|
||||
}
|
||||
|
||||
// TODO: try to removve global symbols
|
||||
let symbolsLoaded = false;
|
||||
let symbols: string[] = [];
|
||||
export function SetupTextEditor(): void {
|
||||
const ns = NetscriptFunctions({} as WorkerScript);
|
||||
|
||||
// Populates symbols for text editor
|
||||
function populate(ns: any): string[] {
|
||||
let symbols: string[] = [];
|
||||
const keys = Object.keys(ns);
|
||||
@ -53,41 +66,48 @@ export function SetupTextEditor(): void {
|
||||
symbols.push(key);
|
||||
}
|
||||
}
|
||||
|
||||
return symbols;
|
||||
}
|
||||
|
||||
symbols = populate(ns);
|
||||
|
||||
const exclude = ["heart", "break", "exploit", "bypass", "corporation", "alterReality"];
|
||||
symbols = symbols.filter((symbol: string) => !exclude.includes(symbol)).sort();
|
||||
}
|
||||
|
||||
interface IProps {
|
||||
filename: string;
|
||||
|
||||
// Holds all the data for a open script
|
||||
class OpenScript {
|
||||
fileName: string;
|
||||
code: string;
|
||||
hostname: string;
|
||||
player: IPlayer;
|
||||
router: IRouter;
|
||||
lastPosition: monaco.Position;
|
||||
model: ITextModel;
|
||||
|
||||
constructor(fileName: string, code: string, hostname: string, lastPosition: monaco.Position, model: ITextModel) {
|
||||
this.fileName = fileName;
|
||||
this.code = code;
|
||||
this.hostname = hostname;
|
||||
this.lastPosition = lastPosition;
|
||||
this.model = model;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
// Called every time script editor is opened
|
||||
export function Root(props: IProps): React.ReactElement {
|
||||
const editorRef = useRef<IStandaloneCodeEditor | null>(null);
|
||||
const monacoRef = useRef<Monaco | null>(null);
|
||||
|
||||
*/
|
||||
const [openScripts, setOpenScripts] = useState<OpenScript[]>(
|
||||
window.localStorage.getItem('scriptEditorOpenScripts') !== null ? JSON.parse(window.localStorage.getItem('scriptEditorOpenScripts')!) : []
|
||||
);
|
||||
|
||||
// How to load function definition in monaco
|
||||
// https://github.com/Microsoft/monaco-editor/issues/1415
|
||||
// https://microsoft.github.io/monaco-editor/api/modules/monaco.languages.html
|
||||
// https://www.npmjs.com/package/@monaco-editor/react#development-playground
|
||||
// https://microsoft.github.io/monaco-editor/playground.html#extending-language-services-custom-languages
|
||||
// https://github.com/threehams/typescript-error-guide/blob/master/stories/components/Editor.tsx#L11-L39
|
||||
// https://blog.checklyhq.com/customizing-monaco/
|
||||
|
||||
// These variables are used to reload a script when it's clicked on. Because we
|
||||
// won't have references to the old script.
|
||||
let lastFilename = "";
|
||||
let lastCode = "";
|
||||
let hostname = "";
|
||||
let lastPosition: monaco.Position | null = null;
|
||||
const [currentScript, setCurrentScript] = useState<OpenScript | null>(
|
||||
window.localStorage.getItem('scriptEditorCurrentScript') !== null ? JSON.parse(window.localStorage.getItem('scriptEditorCurrentScript')!) : null
|
||||
);
|
||||
|
||||
<<<<<<< HEAD
|
||||
export function Root(props: IProps): React.ReactElement {
|
||||
const editorRef = useRef<IStandaloneCodeEditor | null>(null);
|
||||
const vimStatusRef = useRef<HTMLElement>(null);
|
||||
@ -100,8 +120,12 @@ export function Root(props: IProps): React.ReactElement {
|
||||
if (hostname === "") {
|
||||
hostname = props.player.getCurrentServer().hostname;
|
||||
}
|
||||
=======
|
||||
>>>>>>> dev
|
||||
const [ram, setRAM] = useState("RAM: ???");
|
||||
const [updatingRam, setUpdatingRam] = useState(false);
|
||||
const [decorations, setDecorations] = useState<string[]>([]);
|
||||
|
||||
const [optionsOpen, setOptionsOpen] = useState(false);
|
||||
const [options, setOptions] = useState<Options>({
|
||||
theme: Settings.MonacoTheme,
|
||||
@ -110,202 +134,26 @@ export function Root(props: IProps): React.ReactElement {
|
||||
vim: Settings.MonacoVim,
|
||||
});
|
||||
|
||||
const debouncedSetRAM = useMemo(
|
||||
() =>
|
||||
debounce((s) => {
|
||||
setRAM(s);
|
||||
setUpdatingRam(false);
|
||||
}, 300),
|
||||
[],
|
||||
);
|
||||
|
||||
// store the last known state in case we need to restart without nano.
|
||||
useEffect(() => {
|
||||
if (props.filename === undefined) return;
|
||||
lastFilename = props.filename;
|
||||
lastCode = props.code;
|
||||
lastPosition = null;
|
||||
}, []);
|
||||
// Save currentScript
|
||||
window.localStorage.setItem('scriptEditorCurrentScript', JSON.stringify(currentScript, (key, value) => {
|
||||
if (key == 'model') return undefined;
|
||||
return value;
|
||||
}));
|
||||
|
||||
function save(): void {
|
||||
if (editorRef.current !== null) {
|
||||
const position = editorRef.current.getPosition();
|
||||
if (position !== null) {
|
||||
CursorPositions.saveCursor(filename, {
|
||||
row: position.lineNumber,
|
||||
column: position.column,
|
||||
});
|
||||
}
|
||||
}
|
||||
lastPosition = null;
|
||||
// Save openScripts
|
||||
window.localStorage.setItem('scriptEditorOpenScripts', JSON.stringify(openScripts, (key, value) => {
|
||||
if (key == 'model') return undefined;
|
||||
return value;
|
||||
}))
|
||||
}, [currentScript, openScripts])
|
||||
|
||||
// this is duplicate code with saving later.
|
||||
if (ITutorial.isRunning && ITutorial.currStep === iTutorialSteps.TerminalTypeScript) {
|
||||
//Make sure filename + code properly follow tutorial
|
||||
if (filename !== "n00dles.script") {
|
||||
dialogBoxCreate("Leave the script name as 'n00dles.script'!");
|
||||
return;
|
||||
}
|
||||
if (code.replace(/\s/g, "").indexOf("while(true){hack('n00dles');}") == -1) {
|
||||
dialogBoxCreate("Please copy and paste the code from the tutorial!");
|
||||
return;
|
||||
}
|
||||
|
||||
//Save the script
|
||||
const server = GetServer(hostname);
|
||||
if (server === null) throw new Error("Server should not be null but it is.");
|
||||
let found = false;
|
||||
for (let i = 0; i < server.scripts.length; i++) {
|
||||
if (filename == server.scripts[i].filename) {
|
||||
server.scripts[i].saveScript(filename, code, hostname, server.scripts);
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
const script = new Script();
|
||||
script.saveScript(filename, code, hostname, server.scripts);
|
||||
server.scripts.push(script);
|
||||
}
|
||||
|
||||
iTutorialNextStep();
|
||||
|
||||
props.router.toTerminal();
|
||||
return;
|
||||
}
|
||||
|
||||
if (filename == "") {
|
||||
dialogBoxCreate("You must specify a filename!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isValidFilePath(filename)) {
|
||||
dialogBoxCreate(
|
||||
"Script filename can contain only alphanumerics, hyphens, and underscores, and must end with an extension.",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const server = GetServer(hostname);
|
||||
if (server === null) throw new Error("Server should not be null but it is.");
|
||||
if (isScriptFilename(filename)) {
|
||||
//If the current script already exists on the server, overwrite it
|
||||
for (let i = 0; i < server.scripts.length; i++) {
|
||||
if (filename == server.scripts[i].filename) {
|
||||
server.scripts[i].saveScript(filename, code, props.player.currentServer, server.scripts);
|
||||
if (Settings.SaveGameOnFileSave) saveObject.saveGame();
|
||||
props.router.toTerminal();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
//If the current script does NOT exist, create a new one
|
||||
const script = new Script();
|
||||
script.saveScript(filename, code, props.player.currentServer, server.scripts);
|
||||
server.scripts.push(script);
|
||||
} else if (filename.endsWith(".txt")) {
|
||||
for (let i = 0; i < server.textFiles.length; ++i) {
|
||||
if (server.textFiles[i].fn === filename) {
|
||||
server.textFiles[i].write(code);
|
||||
if (Settings.SaveGameOnFileSave) saveObject.saveGame();
|
||||
props.router.toTerminal();
|
||||
return;
|
||||
}
|
||||
}
|
||||
const textFile = new TextFile(filename, code);
|
||||
server.textFiles.push(textFile);
|
||||
} else {
|
||||
dialogBoxCreate("Invalid filename. Must be either a script (.script, .js, or .ns) or " + " or text file (.txt)");
|
||||
return;
|
||||
}
|
||||
|
||||
if (Settings.SaveGameOnFileSave) saveObject.saveGame();
|
||||
props.router.toTerminal();
|
||||
}
|
||||
|
||||
function beautify(): void {
|
||||
if (editorRef.current === null) return;
|
||||
editorRef.current.getAction("editor.action.formatDocument").run();
|
||||
}
|
||||
|
||||
function onFilenameChange(event: React.ChangeEvent<HTMLInputElement>): void {
|
||||
lastFilename = event.target.value;
|
||||
setFilename(event.target.value);
|
||||
}
|
||||
|
||||
function infLoop(newCode: string): void {
|
||||
if (editorRef.current === null) return;
|
||||
if (!filename.endsWith(".ns") && !filename.endsWith(".js")) return;
|
||||
const awaitWarning = checkInfiniteLoop(newCode);
|
||||
if (awaitWarning !== -1) {
|
||||
const newDecorations = editorRef.current.deltaDecorations(decorations, [
|
||||
{
|
||||
range: {
|
||||
startLineNumber: awaitWarning,
|
||||
startColumn: 1,
|
||||
endLineNumber: awaitWarning,
|
||||
endColumn: 10,
|
||||
},
|
||||
options: {
|
||||
isWholeLine: true,
|
||||
glyphMarginClassName: "myGlyphMarginClass",
|
||||
glyphMarginHoverMessage: {
|
||||
value: "Possible infinite loop, await something.",
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
setDecorations(newDecorations);
|
||||
} else {
|
||||
const newDecorations = editorRef.current.deltaDecorations(decorations, []);
|
||||
setDecorations(newDecorations);
|
||||
}
|
||||
}
|
||||
|
||||
function updateCode(newCode?: string): void {
|
||||
if (newCode === undefined) return;
|
||||
lastCode = newCode;
|
||||
setCode(newCode);
|
||||
updateRAM(newCode);
|
||||
try {
|
||||
if (editorRef.current !== null) {
|
||||
lastPosition = editorRef.current.getPosition();
|
||||
infLoop(newCode);
|
||||
}
|
||||
} catch (err) {}
|
||||
}
|
||||
|
||||
// calculate it once the first time the file is loaded.
|
||||
useEffect(() => {
|
||||
updateRAM(code);
|
||||
if (currentScript !== null) {
|
||||
updateRAM(currentScript.code);
|
||||
}
|
||||
}, []);
|
||||
|
||||
async function updateRAM(newCode: string): Promise<void> {
|
||||
setUpdatingRam(true);
|
||||
const codeCopy = newCode + "";
|
||||
const ramUsage = await calculateRamUsage(codeCopy, props.player.getCurrentServer().scripts);
|
||||
if (ramUsage > 0) {
|
||||
debouncedSetRAM("RAM: " + numeralWrapper.formatRAM(ramUsage));
|
||||
return;
|
||||
}
|
||||
switch (ramUsage) {
|
||||
case RamCalculationErrorCode.ImportError: {
|
||||
debouncedSetRAM("RAM: Import Error");
|
||||
break;
|
||||
}
|
||||
case RamCalculationErrorCode.URLImportError: {
|
||||
debouncedSetRAM("RAM: HTTP Import Error");
|
||||
break;
|
||||
}
|
||||
case RamCalculationErrorCode.SyntaxError:
|
||||
default: {
|
||||
debouncedSetRAM("RAM: Syntax Error");
|
||||
break;
|
||||
}
|
||||
}
|
||||
return new Promise<void>(() => undefined);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
function maybeSave(event: KeyboardEvent): void {
|
||||
if (Settings.DisableHotkeys) return;
|
||||
@ -314,6 +162,13 @@ export function Root(props: IProps): React.ReactElement {
|
||||
event.preventDefault();
|
||||
save();
|
||||
}
|
||||
|
||||
// CTRL/CMD + S
|
||||
if (event.code == `KeyS` && (event.ctrlKey || event.metaKey)) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
save();
|
||||
}
|
||||
}
|
||||
document.addEventListener("keydown", maybeSave);
|
||||
return () => document.removeEventListener("keydown", maybeSave);
|
||||
@ -348,29 +203,65 @@ export function Root(props: IProps): React.ReactElement {
|
||||
};
|
||||
}, [options, editorRef, editor, vimEditor]);
|
||||
|
||||
function onMount(editor: IStandaloneCodeEditor): void {
|
||||
// Required when switching between site navigation (e.g. from Script Editor -> Terminal and back)
|
||||
// the `useEffect()` for vim mode is called before editor is mounted.
|
||||
setEditor(editor);
|
||||
editorRef.current = editor;
|
||||
if (editorRef.current === null) return;
|
||||
const position = CursorPositions.getCursor(filename);
|
||||
if (position.row !== -1)
|
||||
editorRef.current.setPosition({
|
||||
lineNumber: position.row,
|
||||
column: position.column,
|
||||
});
|
||||
else if (lastPosition !== null)
|
||||
editorRef.current.setPosition({
|
||||
lineNumber: lastPosition.lineNumber,
|
||||
column: lastPosition.column + 1,
|
||||
});
|
||||
|
||||
editorRef.current.focus();
|
||||
// Generates a new model for the script
|
||||
function regenerateModel(script: OpenScript): void {
|
||||
if (monacoRef.current !== null) {
|
||||
script.model = monacoRef.current.editor.createModel(script.code, "javascript");
|
||||
}
|
||||
}
|
||||
|
||||
const debouncedSetRAM = useMemo(
|
||||
() =>
|
||||
debounce((s) => {
|
||||
setRAM(s);
|
||||
setUpdatingRam(false);
|
||||
}, 300),
|
||||
[],
|
||||
);
|
||||
|
||||
async function updateRAM(newCode: string): Promise<void> {
|
||||
setUpdatingRam(true);
|
||||
const codeCopy = newCode + "";
|
||||
const ramUsage = await calculateRamUsage(codeCopy, props.player.getCurrentServer().scripts);
|
||||
if (ramUsage > 0) {
|
||||
debouncedSetRAM("RAM: " + numeralWrapper.formatRAM(ramUsage));
|
||||
return;
|
||||
}
|
||||
switch (ramUsage) {
|
||||
case RamCalculationErrorCode.ImportError: {
|
||||
debouncedSetRAM("RAM: Import Error");
|
||||
break;
|
||||
}
|
||||
case RamCalculationErrorCode.URLImportError: {
|
||||
debouncedSetRAM("RAM: HTTP Import Error");
|
||||
break;
|
||||
}
|
||||
case RamCalculationErrorCode.SyntaxError:
|
||||
default: {
|
||||
debouncedSetRAM("RAM: Syntax Error");
|
||||
break;
|
||||
}
|
||||
}
|
||||
return new Promise<void>(() => undefined);
|
||||
}
|
||||
|
||||
// Formats the code
|
||||
function beautify(): void {
|
||||
if (editorRef.current === null) return;
|
||||
editorRef.current.getAction("editor.action.formatDocument").run();
|
||||
}
|
||||
|
||||
// How to load function definition in monaco
|
||||
// https://github.com/Microsoft/monaco-editor/issues/1415
|
||||
// https://microsoft.github.io/monaco-editor/api/modules/monaco.languages.html
|
||||
// https://www.npmjs.com/package/@monaco-editor/react#development-playground
|
||||
// https://microsoft.github.io/monaco-editor/playground.html#extending-language-services-custom-languages
|
||||
// https://github.com/threehams/typescript-error-guide/blob/master/stories/components/Editor.tsx#L11-L39
|
||||
// https://blog.checklyhq.com/customizing-monaco/
|
||||
// Before the editor is mounted
|
||||
function beforeMount(monaco: any): void {
|
||||
if (symbolsLoaded) return;
|
||||
// Setup monaco auto completion
|
||||
symbolsLoaded = true;
|
||||
monaco.languages.registerCompletionItemProvider("javascript", {
|
||||
provideCompletionItems: () => {
|
||||
@ -386,6 +277,7 @@ export function Root(props: IProps): React.ReactElement {
|
||||
return { suggestions: suggestions };
|
||||
},
|
||||
});
|
||||
|
||||
(async function () {
|
||||
// We have to improve the default js language otherwise theme sucks
|
||||
const l = await monaco.languages
|
||||
@ -407,6 +299,319 @@ export function Root(props: IProps): React.ReactElement {
|
||||
loadThemes(monaco);
|
||||
}
|
||||
|
||||
|
||||
// When the editor is mounted
|
||||
function onMount(editor: IStandaloneCodeEditor, monaco: Monaco): void {
|
||||
// Required when switching between site navigation (e.g. from Script Editor -> Terminal and back)
|
||||
// the `useEffect()` for vim mode is called before editor is mounted.
|
||||
setEditor(editor);
|
||||
|
||||
editorRef.current = editor;
|
||||
monacoRef.current = monaco;
|
||||
|
||||
if (editorRef.current === null || monacoRef.current === null) return;
|
||||
|
||||
if (props.filename) {
|
||||
// Check if file is already opened
|
||||
const openScriptIndex = openScripts.findIndex(script => script.fileName === props.filename && script.hostname === props.hostname);
|
||||
if (openScriptIndex !== -1) {
|
||||
// Script is already opened
|
||||
if (openScripts[openScriptIndex].model === undefined || openScripts[openScriptIndex].model === null || openScripts[openScriptIndex].model.isDisposed()) {
|
||||
regenerateModel(openScripts[openScriptIndex]);
|
||||
}
|
||||
|
||||
setCurrentScript(openScripts[openScriptIndex]);
|
||||
editorRef.current.setModel(openScripts[openScriptIndex].model);
|
||||
editorRef.current.setPosition(openScripts[openScriptIndex].lastPosition);
|
||||
editorRef.current.revealLineInCenter(openScripts[openScriptIndex].lastPosition.lineNumber);
|
||||
updateRAM(openScripts[openScriptIndex].code);
|
||||
} else {
|
||||
// Open script
|
||||
const newScript = new OpenScript(props.filename, props.code, props.hostname, new monacoRef.current.Position(0, 0), monacoRef.current.editor.createModel(props.code, 'javascript'));
|
||||
setOpenScripts(oldArray => [...oldArray, newScript]);
|
||||
setCurrentScript({ ...newScript });
|
||||
editorRef.current.setModel(newScript.model);
|
||||
updateRAM(newScript.code);
|
||||
}
|
||||
} else if (currentScript !== null) {
|
||||
// Open currentscript
|
||||
regenerateModel(currentScript);
|
||||
editorRef.current.setModel(currentScript.model);
|
||||
editorRef.current.setPosition(currentScript.lastPosition);
|
||||
editorRef.current.revealLineInCenter(currentScript.lastPosition.lineNumber);
|
||||
updateRAM(currentScript.code);
|
||||
}
|
||||
|
||||
editorRef.current.focus();
|
||||
}
|
||||
|
||||
function infLoop(newCode: string): void {
|
||||
if (editorRef.current === null || currentScript === null) return;
|
||||
if (!currentScript.fileName.endsWith(".ns") && !currentScript.fileName.endsWith(".js")) return;
|
||||
const awaitWarning = checkInfiniteLoop(newCode);
|
||||
if (awaitWarning !== -1) {
|
||||
const newDecorations = editorRef.current.deltaDecorations(decorations, [
|
||||
{
|
||||
range: {
|
||||
startLineNumber: awaitWarning,
|
||||
startColumn: 1,
|
||||
endLineNumber: awaitWarning,
|
||||
endColumn: 10,
|
||||
},
|
||||
options: {
|
||||
isWholeLine: true,
|
||||
glyphMarginClassName: "myGlyphMarginClass",
|
||||
glyphMarginHoverMessage: {
|
||||
value: "Possible infinite loop, await something.",
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
setDecorations(newDecorations);
|
||||
} else {
|
||||
const newDecorations = editorRef.current.deltaDecorations(decorations, []);
|
||||
setDecorations(newDecorations);
|
||||
}
|
||||
}
|
||||
|
||||
// When the code is updated within the editor
|
||||
function updateCode(newCode?: string): void {
|
||||
if (newCode === undefined) return;
|
||||
updateRAM(newCode);
|
||||
if (editorRef.current !== null) {
|
||||
const newPos = editorRef.current.getPosition();
|
||||
if (newPos === null) return;
|
||||
setCurrentScript(oldScript => ({ ...oldScript!, code: newCode, lastPosition: newPos! }))
|
||||
if (currentScript !== null) {
|
||||
const curIndex = openScripts.findIndex(script => script.fileName === currentScript.fileName && script.hostname === currentScript.hostname);
|
||||
const newArr = [...openScripts];
|
||||
const tempScript = currentScript;
|
||||
tempScript.code = newCode;
|
||||
newArr[curIndex] = tempScript;
|
||||
setOpenScripts([...newArr]);
|
||||
}
|
||||
try {
|
||||
infLoop(newCode);
|
||||
} catch (err) { }
|
||||
}
|
||||
}
|
||||
|
||||
function saveScript(scriptToSave: OpenScript): void {
|
||||
const server = GetServer(scriptToSave.hostname);
|
||||
if (server === null) throw new Error("Server should not be null but it is.");
|
||||
if (isScriptFilename(scriptToSave.fileName)) {
|
||||
//If the current script already exists on the server, overwrite it
|
||||
for (let i = 0; i < server.scripts.length; i++) {
|
||||
if (scriptToSave.fileName == server.scripts[i].filename) {
|
||||
server.scripts[i].saveScript(
|
||||
scriptToSave.fileName,
|
||||
scriptToSave.code,
|
||||
props.player.currentServer,
|
||||
server.scripts,
|
||||
);
|
||||
if (Settings.SaveGameOnFileSave) saveObject.saveGame();
|
||||
props.router.toTerminal();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
//If the current script does NOT exist, create a new one
|
||||
const script = new Script();
|
||||
script.saveScript(scriptToSave.fileName, scriptToSave.code, props.player.currentServer, server.scripts);
|
||||
server.scripts.push(script);
|
||||
} else if (scriptToSave.fileName.endsWith(".txt")) {
|
||||
for (let i = 0; i < server.textFiles.length; ++i) {
|
||||
if (server.textFiles[i].fn === scriptToSave.fileName) {
|
||||
server.textFiles[i].write(scriptToSave.code);
|
||||
if (Settings.SaveGameOnFileSave) saveObject.saveGame();
|
||||
props.router.toTerminal();
|
||||
return;
|
||||
}
|
||||
}
|
||||
const textFile = new TextFile(scriptToSave.fileName, scriptToSave.code);
|
||||
server.textFiles.push(textFile);
|
||||
} else {
|
||||
dialogBoxCreate("Invalid filename. Must be either a script (.script, .js, or .ns) or " + " or text file (.txt)");
|
||||
return;
|
||||
}
|
||||
|
||||
if (Settings.SaveGameOnFileSave) saveObject.saveGame();
|
||||
props.router.toTerminal();
|
||||
}
|
||||
|
||||
function save(): void {
|
||||
if (currentScript === null) {
|
||||
console.log("currentScript is null when it shouldn't be. Unabel to save script");
|
||||
return;
|
||||
}
|
||||
// this is duplicate code with saving later.
|
||||
if (ITutorial.isRunning && ITutorial.currStep === iTutorialSteps.TerminalTypeScript) {
|
||||
//Make sure filename + code properly follow tutorial
|
||||
if (currentScript.fileName !== "n00dles.script") {
|
||||
dialogBoxCreate("Leave the script name as 'n00dles.script'!");
|
||||
return;
|
||||
}
|
||||
if (currentScript.code.replace(/\s/g, "").indexOf("while(true){hack('n00dles');}") == -1) {
|
||||
dialogBoxCreate("Please copy and paste the code from the tutorial!");
|
||||
return;
|
||||
}
|
||||
|
||||
//Save the script
|
||||
saveScript(currentScript);
|
||||
|
||||
iTutorialNextStep();
|
||||
|
||||
props.router.toTerminal();
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentScript.fileName == "") {
|
||||
dialogBoxCreate("You must specify a filename!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isValidFilePath(currentScript.fileName)) {
|
||||
dialogBoxCreate(
|
||||
"Script filename can contain only alphanumerics, hyphens, and underscores, and must end with an extension.",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const server = GetServer(currentScript.hostname);
|
||||
if (server === null) throw new Error("Server should not be null but it is.");
|
||||
if (isScriptFilename(currentScript.fileName)) {
|
||||
//If the current script already exists on the server, overwrite it
|
||||
for (let i = 0; i < server.scripts.length; i++) {
|
||||
if (currentScript.fileName == server.scripts[i].filename) {
|
||||
server.scripts[i].saveScript(
|
||||
currentScript.fileName,
|
||||
currentScript.code,
|
||||
props.player.currentServer,
|
||||
server.scripts,
|
||||
);
|
||||
if (Settings.SaveGameOnFileSave) saveObject.saveGame();
|
||||
props.router.toTerminal();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
//If the current script does NOT exist, create a new one
|
||||
const script = new Script();
|
||||
script.saveScript(currentScript.fileName, currentScript.code, props.player.currentServer, server.scripts);
|
||||
server.scripts.push(script);
|
||||
} else if (currentScript.fileName.endsWith(".txt")) {
|
||||
for (let i = 0; i < server.textFiles.length; ++i) {
|
||||
if (server.textFiles[i].fn === currentScript.fileName) {
|
||||
server.textFiles[i].write(currentScript.code);
|
||||
if (Settings.SaveGameOnFileSave) saveObject.saveGame();
|
||||
props.router.toTerminal();
|
||||
return;
|
||||
}
|
||||
}
|
||||
const textFile = new TextFile(currentScript.fileName, currentScript.code);
|
||||
server.textFiles.push(textFile);
|
||||
} else {
|
||||
dialogBoxCreate("Invalid filename. Must be either a script (.script, .js, or .ns) or " + " or text file (.txt)");
|
||||
return;
|
||||
}
|
||||
|
||||
if (Settings.SaveGameOnFileSave) saveObject.saveGame();
|
||||
props.router.toTerminal();
|
||||
}
|
||||
|
||||
function reorder(list: Array<OpenScript>, startIndex: number, endIndex: number): OpenScript[] {
|
||||
const result = Array.from(list);
|
||||
const [removed] = result.splice(startIndex, 1);
|
||||
result.splice(endIndex, 0, removed);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function onDragEnd(result: any): void{
|
||||
// Dropped outside of the list
|
||||
if (!result.destination) {
|
||||
result
|
||||
return;
|
||||
}
|
||||
|
||||
const items = reorder(openScripts, result.source.index, result.destination.index);
|
||||
|
||||
setOpenScripts(items);
|
||||
}
|
||||
|
||||
function onTabClick(index: number): void {
|
||||
if (currentScript !== null) {
|
||||
// Save currentScript to openScripts
|
||||
const curIndex = openScripts.findIndex(script => script.fileName === currentScript.fileName && script.hostname === currentScript.hostname);
|
||||
openScripts[curIndex] = currentScript;
|
||||
}
|
||||
|
||||
setCurrentScript({ ...openScripts[index] });
|
||||
|
||||
if (editorRef.current !== null && openScripts[index] !== null) {
|
||||
if (openScripts[index].model === undefined || openScripts[index].model.isDisposed()) {
|
||||
regenerateModel(openScripts[index]);
|
||||
}
|
||||
editorRef.current.setModel(openScripts[index].model);
|
||||
|
||||
editorRef.current.setPosition(openScripts[index].lastPosition);
|
||||
editorRef.current.revealLineInCenter(openScripts[index].lastPosition.lineNumber);
|
||||
updateRAM(openScripts[index].code);
|
||||
editorRef.current.focus();
|
||||
}
|
||||
}
|
||||
|
||||
async function onTabClose(index: number): Promise<void> {
|
||||
// See if the script on the server is up to date
|
||||
const closingScript = openScripts[index];
|
||||
const savedOpenScripts: Array<OpenScript> = JSON.parse(window.localStorage.getItem('scriptEditorOpenScripts')!);
|
||||
const savedScriptIndex = savedOpenScripts.findIndex(script => script.fileName === closingScript.fileName && script.hostname === closingScript.hostname);
|
||||
let savedScriptCode = '';
|
||||
if (savedScriptIndex !== -1) {
|
||||
savedScriptCode = savedOpenScripts[savedScriptIndex].code;
|
||||
}
|
||||
|
||||
const serverScriptIndex = GetServer(closingScript.hostname)?.scripts.findIndex(script => script.filename === closingScript.fileName);
|
||||
if (serverScriptIndex === -1 || savedScriptCode !== GetServer(closingScript.hostname)?.scripts[serverScriptIndex as number].code) {
|
||||
PromptEvent.emit({
|
||||
txt: 'Do you want to save changes to ' + closingScript.fileName + '?',
|
||||
resolve: (result: boolean) => {
|
||||
if (result) {
|
||||
// Save changes
|
||||
closingScript.code = savedScriptCode;
|
||||
saveScript(closingScript);
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (openScripts.length > 1) {
|
||||
setOpenScripts(oldScripts => oldScripts.filter((value, i) => i !== index));
|
||||
|
||||
let indexOffset = -1;
|
||||
if (openScripts[index + indexOffset] === undefined) {
|
||||
indexOffset = 1;
|
||||
}
|
||||
|
||||
// Change current script if we closed it
|
||||
setCurrentScript(openScripts[index + indexOffset]);
|
||||
if (editorRef.current !== null) {
|
||||
if (openScripts[index + indexOffset].model === undefined || openScripts[index + indexOffset].model === null || openScripts[index + indexOffset].model.isDisposed()) {
|
||||
regenerateModel(openScripts[index + indexOffset]);
|
||||
}
|
||||
|
||||
editorRef.current.setModel(openScripts[index + indexOffset].model);
|
||||
editorRef.current.setPosition(openScripts[index + indexOffset].lastPosition);
|
||||
editorRef.current.revealLineInCenter(openScripts[index + indexOffset].lastPosition.lineNumber)
|
||||
editorRef.current.focus();
|
||||
}
|
||||
} else {
|
||||
// No more scripts are open
|
||||
setOpenScripts([]);
|
||||
setCurrentScript(null);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Make this responsive to window resizes
|
||||
// Toolbars are roughly 108px + vim bar 34px
|
||||
// Get percentage of space that toolbars represent and the rest should be the
|
||||
@ -415,78 +620,112 @@ export function Root(props: IProps): React.ReactElement {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box display="flex" flexDirection="row" alignItems="center">
|
||||
<TextField
|
||||
placeholder="filename"
|
||||
type="text"
|
||||
tabIndex={1}
|
||||
value={filename}
|
||||
onChange={onFilenameChange}
|
||||
InputProps={{ startAdornment: <Typography>{hostname}:~/</Typography> }}
|
||||
<div style={{ display: currentScript !== null ? 'block' : 'none', height: '100%', width: '100%' }}>
|
||||
<DragDropContext onDragEnd={onDragEnd}>
|
||||
<Droppable droppableId='tabs' direction='horizontal'>
|
||||
{(provided, snapshot) => (
|
||||
<Box
|
||||
maxWidth="1640px"
|
||||
display="flex"
|
||||
flexDirection="row"
|
||||
alignItems="center"
|
||||
whiteSpace="nowrap"
|
||||
ref={provided.innerRef}
|
||||
{...provided.droppableProps}
|
||||
style={{ backgroundColor: snapshot.isDraggingOver ? '#1F2022' : Settings.theme.backgroundprimary, overflowX: 'scroll' }}
|
||||
>
|
||||
{openScripts.map(({ fileName, hostname }, index) => (
|
||||
<Draggable key={fileName + hostname} draggableId={fileName + hostname} index={index} disableInteractiveElementBlocking={true}>
|
||||
{(provided) => (
|
||||
<div
|
||||
ref={provided.innerRef}
|
||||
{...provided.draggableProps}
|
||||
{...provided.dragHandleProps}
|
||||
style={{
|
||||
...provided.draggableProps.style,
|
||||
marginRight: '5px',
|
||||
flexShrink: 0
|
||||
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
id={"tabButton" + fileName + hostname}
|
||||
onClick={() => onTabClick(index)}
|
||||
style={{ background: currentScript?.fileName === openScripts[index].fileName ? Settings.theme.secondarydark : '' }}
|
||||
>
|
||||
{hostname}:~/{fileName}
|
||||
</Button>
|
||||
<Button
|
||||
id={"tabCloseButton" + fileName + hostname}
|
||||
onClick={() => onTabClose(index)}
|
||||
style={{ maxWidth: "20px", minWidth: "20px", background: currentScript?.fileName === openScripts[index].fileName ? Settings.theme.secondarydark : '' }}
|
||||
>
|
||||
x
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</Draggable>
|
||||
))}
|
||||
{provided.placeholder}
|
||||
</Box>
|
||||
)}
|
||||
</Droppable>
|
||||
</DragDropContext>
|
||||
<div style={{ paddingBottom: '5px' }} />
|
||||
<Editor
|
||||
beforeMount={beforeMount}
|
||||
onMount={onMount}
|
||||
loading={<Typography>Loading script editor!</Typography>}
|
||||
height={`${editorHeight}%`}
|
||||
defaultLanguage="javascript"
|
||||
defaultValue={''}
|
||||
onChange={updateCode}
|
||||
theme={options.theme}
|
||||
options={{ ...options, glyphMargin: true }}
|
||||
/>
|
||||
<IconButton onClick={() => setOptionsOpen(true)}>
|
||||
<>
|
||||
<SettingsIcon />
|
||||
options
|
||||
</>
|
||||
</IconButton>
|
||||
</Box>
|
||||
<Editor
|
||||
beforeMount={beforeMount}
|
||||
onMount={onMount}
|
||||
loading={<Typography>Loading script editor!</Typography>}
|
||||
height={`${editorHeight}%`}
|
||||
defaultLanguage="javascript"
|
||||
defaultValue={code}
|
||||
onChange={updateCode}
|
||||
theme={options.theme}
|
||||
options={{ ...options, glyphMargin: true }}
|
||||
/>
|
||||
|
||||
<Box
|
||||
ref={vimStatusRef}
|
||||
className="monaco-editor"
|
||||
display="flex"
|
||||
flexDirection="row"
|
||||
sx={{ p: 1 }}
|
||||
alignItems="center"
|
||||
></Box>
|
||||
|
||||
<Box display="flex" flexDirection="row" sx={{ m: 1 }} alignItems="center">
|
||||
<Button onClick={beautify}>Beautify</Button>
|
||||
<Typography color={updatingRam ? "secondary" : "primary"} sx={{ mx: 1 }}>
|
||||
{ram}
|
||||
</Typography>
|
||||
<Button onClick={save}>Save & Close (Ctrl/Cmd + b)</Button>
|
||||
<Typography sx={{ mx: 1 }}>
|
||||
{" "}
|
||||
Documentation:{" "}
|
||||
<Link target="_blank" href="https://bitburner.readthedocs.io/en/latest/index.html">
|
||||
Basic
|
||||
</Link>{" "}
|
||||
|
|
||||
<Link target="_blank" href="https://github.com/danielyxie/bitburner/blob/dev/markdown/bitburner.ns.md">
|
||||
Full
|
||||
</Link>
|
||||
</Typography>
|
||||
</Box>
|
||||
<OptionsModal
|
||||
open={optionsOpen}
|
||||
onClose={() => setOptionsOpen(false)}
|
||||
options={{
|
||||
theme: Settings.MonacoTheme,
|
||||
insertSpaces: Settings.MonacoInsertSpaces,
|
||||
fontSize: Settings.MonacoFontSize,
|
||||
vim: Settings.MonacoVim,
|
||||
}}
|
||||
save={(options: Options) => {
|
||||
setOptions(options);
|
||||
Settings.MonacoTheme = options.theme;
|
||||
Settings.MonacoInsertSpaces = options.insertSpaces;
|
||||
Settings.MonacoFontSize = options.fontSize;
|
||||
Settings.MonacoVim = options.vim;
|
||||
}}
|
||||
/>
|
||||
<Box display="flex" flexDirection="row" sx={{ m: 1 }} alignItems="center">
|
||||
<Button onClick={beautify}>Beautify</Button>
|
||||
<Typography color={updatingRam ? "secondary" : "primary"} sx={{ mx: 1 }}>
|
||||
{ram}
|
||||
</Typography>
|
||||
<Button onClick={save}>Save & Close (Ctrl/Cmd + s)</Button>
|
||||
<Typography sx={{ mx: 1 }}>
|
||||
{" "}
|
||||
Documentation:{" "}
|
||||
<Link target="_blank" href="https://bitburner.readthedocs.io/en/latest/index.html">
|
||||
Basic
|
||||
</Link>{" "}
|
||||
|
|
||||
<Link target="_blank" href="https://github.com/danielyxie/bitburner/blob/dev/markdown/bitburner.ns.md">
|
||||
Full
|
||||
</Link>
|
||||
</Typography>
|
||||
<IconButton style={{ marginLeft: "auto" }} onClick={() => setOptionsOpen(true)}>
|
||||
<>
|
||||
<SettingsIcon />
|
||||
options
|
||||
</>
|
||||
</IconButton>
|
||||
</Box>
|
||||
<OptionsModal
|
||||
open={optionsOpen}
|
||||
onClose={() => setOptionsOpen(false)}
|
||||
options={{
|
||||
theme: Settings.MonacoTheme,
|
||||
insertSpaces: Settings.MonacoInsertSpaces,
|
||||
fontSize: Settings.MonacoFontSize,
|
||||
}}
|
||||
save={(options: Options) => {
|
||||
setOptions(options);
|
||||
Settings.MonacoTheme = options.theme;
|
||||
Settings.MonacoInsertSpaces = options.insertSpaces;
|
||||
Settings.MonacoFontSize = options.fontSize;
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div style={{ display: currentScript !== null ? 'none' : 'flex', height: '100%', width: '100%', justifyContent: 'center', alignItems: 'center' }}>
|
||||
<p style={{ color: Settings.theme.primary, fontSize: '20px', textAlign: 'center' }}><h1>No open files</h1><h5>Use "nano [File Name]" in the terminal to open files</h5></p>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
@ -246,6 +246,7 @@ export class BaseServer {
|
||||
this.maxRam = ram;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
updateRamUsed(ram: number, player: IPlayer): void {
|
||||
this.ramUsed = ram;
|
||||
}
|
||||
|
@ -16,22 +16,25 @@ import { isValidNumber } from "../utils/helpers/isValidNumber";
|
||||
* does not have a duplicate hostname/ip.
|
||||
*/
|
||||
export function safetlyCreateUniqueServer(params: IConstructorParams): Server {
|
||||
let hostname: string = params.hostname.replace(/ /g, `-`);
|
||||
|
||||
if (params.ip != null && ipExists(params.ip)) {
|
||||
params.ip = createUniqueRandomIp();
|
||||
}
|
||||
|
||||
if (GetServer(params.hostname) != null) {
|
||||
if (GetServer(hostname) != null) {
|
||||
hostname = `${hostname}-0`;
|
||||
|
||||
// Use a for loop to ensure that we don't get suck in an infinite loop somehow
|
||||
let hostname: string = params.hostname;
|
||||
for (let i = 0; i < 200; ++i) {
|
||||
hostname = `${params.hostname}-${i}`;
|
||||
hostname = hostname.replace(/-[0-9]+$/, `-${i}`);
|
||||
if (GetServer(hostname) == null) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
params.hostname = hostname;
|
||||
}
|
||||
|
||||
params.hostname = hostname;
|
||||
return new Server(params);
|
||||
}
|
||||
|
||||
|
@ -96,7 +96,7 @@ export function purchaseServer(hostname: string, ram: number, cost: number, p: I
|
||||
|
||||
p.loseMoney(cost, "servers");
|
||||
|
||||
dialogBoxCreate("Server successfully purchased with hostname " + hostname);
|
||||
dialogBoxCreate("Server successfully purchased with hostname " + newServ.hostname);
|
||||
}
|
||||
|
||||
// Manually upgrade RAM on home computer (NOT through Netscript)
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { ISelfInitializer, ISelfLoading } from "../types";
|
||||
import { OwnedAugmentationsOrderSetting, PurchaseAugmentationsOrderSetting } from "./SettingEnums";
|
||||
import { defaultTheme, ITheme } from "./Themes";
|
||||
|
||||
/**
|
||||
* Represents the default settings the player could customize.
|
||||
@ -103,45 +104,15 @@ interface IDefaultSettings {
|
||||
*/
|
||||
SuppressTIXPopup: boolean;
|
||||
|
||||
/**
|
||||
* Whether the user should be displayed a toast alert when the game is saved.
|
||||
*/
|
||||
SuppressSavedGameToast: boolean;
|
||||
|
||||
/*
|
||||
* Theme colors
|
||||
*/
|
||||
theme: {
|
||||
[key: string]: string | undefined;
|
||||
primarylight: string;
|
||||
primary: string;
|
||||
primarydark: string;
|
||||
successlight: string;
|
||||
success: string;
|
||||
successdark: string;
|
||||
errorlight: string;
|
||||
error: string;
|
||||
errordark: string;
|
||||
secondarylight: string;
|
||||
secondary: string;
|
||||
secondarydark: string;
|
||||
warninglight: string;
|
||||
warning: string;
|
||||
warningdark: string;
|
||||
infolight: string;
|
||||
info: string;
|
||||
infodark: string;
|
||||
welllight: string;
|
||||
well: string;
|
||||
white: string;
|
||||
black: string;
|
||||
hp: string;
|
||||
money: string;
|
||||
hack: string;
|
||||
combat: string;
|
||||
cha: string;
|
||||
int: string;
|
||||
rep: string;
|
||||
disabled: string;
|
||||
backgroundprimary: string;
|
||||
backgroundsecondary: string;
|
||||
button: string;
|
||||
};
|
||||
theme: ITheme;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -188,42 +159,9 @@ export const defaultSettings: IDefaultSettings = {
|
||||
SuppressTravelConfirmation: false,
|
||||
SuppressBladeburnerPopup: false,
|
||||
SuppressTIXPopup: false,
|
||||
SuppressSavedGameToast: false,
|
||||
|
||||
theme: {
|
||||
primarylight: "#0f0",
|
||||
primary: "#0c0",
|
||||
primarydark: "#090",
|
||||
successlight: "#0f0",
|
||||
success: "#0c0",
|
||||
successdark: "#090",
|
||||
errorlight: "#f00",
|
||||
error: "#c00",
|
||||
errordark: "#900",
|
||||
secondarylight: "#AAA",
|
||||
secondary: "#888",
|
||||
secondarydark: "#666",
|
||||
warninglight: "#ff0",
|
||||
warning: "#cc0",
|
||||
warningdark: "#990",
|
||||
infolight: "#69f",
|
||||
info: "#36c",
|
||||
infodark: "#039",
|
||||
welllight: "#444",
|
||||
well: "#222",
|
||||
white: "#fff",
|
||||
black: "#000",
|
||||
hp: "#dd3434",
|
||||
money: "#ffd700",
|
||||
hack: "#adff2f",
|
||||
combat: "#faffdf",
|
||||
cha: "#a671d1",
|
||||
int: "#6495ed",
|
||||
rep: "#faffdf",
|
||||
disabled: "#66cfbc",
|
||||
backgroundprimary: "#000",
|
||||
backgroundsecondary: "#000",
|
||||
button: "#333",
|
||||
},
|
||||
theme: defaultTheme,
|
||||
};
|
||||
|
||||
/**
|
||||
@ -253,46 +191,13 @@ export const Settings: ISettings & ISelfInitializer & ISelfLoading = {
|
||||
SuppressTravelConfirmation: defaultSettings.SuppressTravelConfirmation,
|
||||
SuppressBladeburnerPopup: defaultSettings.SuppressBladeburnerPopup,
|
||||
SuppressTIXPopup: defaultSettings.SuppressTIXPopup,
|
||||
SuppressSavedGameToast: defaultSettings.SuppressSavedGameToast,
|
||||
MonacoTheme: "monokai",
|
||||
MonacoInsertSpaces: false,
|
||||
MonacoFontSize: 20,
|
||||
MonacoVim: false,
|
||||
|
||||
theme: {
|
||||
primarylight: defaultSettings.theme.primarylight,
|
||||
primary: defaultSettings.theme.primary,
|
||||
primarydark: defaultSettings.theme.primarydark,
|
||||
successlight: defaultSettings.theme.successlight,
|
||||
success: defaultSettings.theme.success,
|
||||
successdark: defaultSettings.theme.successdark,
|
||||
errorlight: defaultSettings.theme.errorlight,
|
||||
error: defaultSettings.theme.error,
|
||||
errordark: defaultSettings.theme.errordark,
|
||||
secondarylight: defaultSettings.theme.secondarylight,
|
||||
secondary: defaultSettings.theme.secondary,
|
||||
secondarydark: defaultSettings.theme.secondarydark,
|
||||
warninglight: defaultSettings.theme.warninglight,
|
||||
warning: defaultSettings.theme.warning,
|
||||
warningdark: defaultSettings.theme.warningdark,
|
||||
infolight: defaultSettings.theme.infolight,
|
||||
info: defaultSettings.theme.info,
|
||||
infodark: defaultSettings.theme.infodark,
|
||||
welllight: defaultSettings.theme.welllight,
|
||||
well: defaultSettings.theme.well,
|
||||
white: defaultSettings.theme.white,
|
||||
black: defaultSettings.theme.black,
|
||||
hp: defaultSettings.theme.hp,
|
||||
money: defaultSettings.theme.money,
|
||||
hack: defaultSettings.theme.hack,
|
||||
combat: defaultSettings.theme.combat,
|
||||
cha: defaultSettings.theme.cha,
|
||||
int: defaultSettings.theme.int,
|
||||
rep: defaultSettings.theme.rep,
|
||||
disabled: defaultSettings.theme.disabled,
|
||||
backgroundprimary: defaultSettings.theme.backgroundprimary,
|
||||
backgroundsecondary: defaultSettings.theme.backgroundsecondary,
|
||||
button: defaultSettings.theme.button,
|
||||
},
|
||||
theme: { ...defaultTheme },
|
||||
init() {
|
||||
Object.assign(Settings, defaultSettings);
|
||||
},
|
||||
|
110
src/Settings/Themes.ts
Normal file
110
src/Settings/Themes.ts
Normal file
@ -0,0 +1,110 @@
|
||||
export interface ITheme {
|
||||
[key: string]: string | undefined;
|
||||
primarylight: string;
|
||||
primary: string;
|
||||
primarydark: string;
|
||||
successlight: string;
|
||||
success: string;
|
||||
successdark: string;
|
||||
errorlight: string;
|
||||
error: string;
|
||||
errordark: string;
|
||||
secondarylight: string;
|
||||
secondary: string;
|
||||
secondarydark: string;
|
||||
warninglight: string;
|
||||
warning: string;
|
||||
warningdark: string;
|
||||
infolight: string;
|
||||
info: string;
|
||||
infodark: string;
|
||||
welllight: string;
|
||||
well: string;
|
||||
white: string;
|
||||
black: string;
|
||||
hp: string;
|
||||
money: string;
|
||||
hack: string;
|
||||
combat: string;
|
||||
cha: string;
|
||||
int: string;
|
||||
rep: string;
|
||||
disabled: string;
|
||||
backgroundprimary: string;
|
||||
backgroundsecondary: string;
|
||||
button: string;
|
||||
}
|
||||
|
||||
export const defaultTheme: ITheme = {
|
||||
primarylight: "#0f0",
|
||||
primary: "#0c0",
|
||||
primarydark: "#090",
|
||||
successlight: "#0f0",
|
||||
success: "#0c0",
|
||||
successdark: "#090",
|
||||
errorlight: "#f00",
|
||||
error: "#c00",
|
||||
errordark: "#900",
|
||||
secondarylight: "#AAA",
|
||||
secondary: "#888",
|
||||
secondarydark: "#666",
|
||||
warninglight: "#ff0",
|
||||
warning: "#cc0",
|
||||
warningdark: "#990",
|
||||
infolight: "#69f",
|
||||
info: "#36c",
|
||||
infodark: "#039",
|
||||
welllight: "#444",
|
||||
well: "#222",
|
||||
white: "#fff",
|
||||
black: "#000",
|
||||
hp: "#dd3434",
|
||||
money: "#ffd700",
|
||||
hack: "#adff2f",
|
||||
combat: "#faffdf",
|
||||
cha: "#a671d1",
|
||||
int: "#6495ed",
|
||||
rep: "#faffdf",
|
||||
disabled: "#66cfbc",
|
||||
backgroundprimary: "#000",
|
||||
backgroundsecondary: "#000",
|
||||
button: "#333",
|
||||
};
|
||||
|
||||
export interface IPredefinedThemes {
|
||||
'Default': ITheme;
|
||||
'Monokai': ITheme;
|
||||
}
|
||||
|
||||
export const getPredefinedThemes = (): IPredefinedThemes => ({
|
||||
'Default': defaultTheme,
|
||||
'Monokai': {
|
||||
...defaultTheme,
|
||||
|
||||
backgroundprimary: '#272822',
|
||||
backgroundsecondary: '#1B1C18',
|
||||
primary: '#F8F8F2',
|
||||
primarylight: '#FFF',
|
||||
primarydark: '#FAFAEB',
|
||||
success: '#A6E22E',
|
||||
successlight: '#ADE146',
|
||||
successdark: '#98E104',
|
||||
error: '#F92672',
|
||||
errorlight: '#FF69A0',
|
||||
errordark: '#D10F56',
|
||||
warning: '#E6DB74',
|
||||
warninglight: '#E1D992',
|
||||
warningdark: '#EDDD54',
|
||||
info: '#66D9EF',
|
||||
infolight: '#92E1F1',
|
||||
infodark: '#31CDED',
|
||||
|
||||
hp: '#F92672',
|
||||
money: '#E6DB74',
|
||||
hack: '#A6E22E',
|
||||
combat: '#75715E',
|
||||
cha: '#AE81FF',
|
||||
int: '#66D9EF',
|
||||
rep: '#E69F66',
|
||||
}
|
||||
});
|
@ -292,7 +292,8 @@ export function SidebarRoot(props: IProps): React.ReactElement {
|
||||
} else if (event.keyCode === KEY.W && event.altKey) {
|
||||
event.preventDefault();
|
||||
clickCity();
|
||||
} else if (event.keyCode === KEY.J && event.altKey) {
|
||||
} else if (event.keyCode === KEY.J && event.altKey && !event.ctrlKey && !event.metaKey && props.player.hasJob()) {
|
||||
// ctrl/cmd + alt + j is shortcut to open Chrome dev tools
|
||||
event.preventDefault();
|
||||
clickJob();
|
||||
} else if (event.keyCode === KEY.R && event.altKey) {
|
||||
|
@ -35,7 +35,7 @@ export function removeTrailingSlash(s: string): string {
|
||||
* Checks whether a string is a valid filename. Only used for the filename itself,
|
||||
* not the entire filepath
|
||||
*/
|
||||
function isValidFilename(filename: string): boolean {
|
||||
export function isValidFilename(filename: string): boolean {
|
||||
// Allows alphanumerics, hyphens, underscores, and percentage signs
|
||||
// Must have a file extension
|
||||
const regex = /^[.a-zA-Z0-9_-]+[.][a-zA-Z0-9]+(?:-\d+(?:\.\d*)?%-INC)?$/;
|
||||
@ -48,7 +48,7 @@ function isValidFilename(filename: string): boolean {
|
||||
* Checks whether a string is a valid directory name. Only used for the directory itself,
|
||||
* not an entire path
|
||||
*/
|
||||
function isValidDirectoryName(name: string): boolean {
|
||||
export function isValidDirectoryName(name: string): boolean {
|
||||
// Allows alphanumerics, hyphens, underscores, and percentage signs.
|
||||
// Name can begin with a single period, but otherwise cannot have any
|
||||
const regex = /^.?[a-zA-Z0-9_-]+$/;
|
||||
@ -178,14 +178,14 @@ export function getAllParentDirectories(path: string): string {
|
||||
* @param cwd The current working directory
|
||||
* @returns A file path which may be absolute or relative
|
||||
*/
|
||||
export function getDestinationFilepath(destination: string, source: string, cwd: string) {
|
||||
export function getDestinationFilepath(destination: string, source: string, cwd: string): string {
|
||||
const dstDir = evaluateDirectoryPath(destination, cwd);
|
||||
// If evaluating the directory for this destination fails, we have a filename or full path.
|
||||
if (dstDir === null) {
|
||||
return destination;
|
||||
} else {
|
||||
// Append the filename to the directory provided.
|
||||
let t_path = removeTrailingSlash(dstDir);
|
||||
const t_path = removeTrailingSlash(dstDir);
|
||||
const fileName = getFileName(source);
|
||||
return t_path + "/" + fileName;
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import { IPlayer } from "../../PersonObjects/IPlayer";
|
||||
import { BaseServer } from "../../Server/BaseServer";
|
||||
import { showMessage } from "../../Message/MessageHelpers";
|
||||
import { showLiterature } from "../../Literature/LiteratureHelpers";
|
||||
import { dialogBoxCreate } from "../../ui/React/DialogBox";
|
||||
|
||||
export function cat(
|
||||
terminal: ITerminal,
|
||||
@ -16,10 +17,11 @@ export function cat(
|
||||
terminal.error("Incorrect usage of cat command. Usage: cat [file]");
|
||||
return;
|
||||
}
|
||||
const filename = terminal.getFilepath(args[0] + "");
|
||||
if (!filename.endsWith(".msg") && !filename.endsWith(".lit") && !filename.endsWith(".txt")) {
|
||||
const relative_filename = args[0] + "";
|
||||
const filename = terminal.getFilepath(relative_filename);
|
||||
if (!filename.endsWith(".msg") && !filename.endsWith(".lit") && !filename.endsWith(".txt") && !filename.endsWith(".script") && !filename.endsWith(".js") && !filename.endsWith(".ns")) {
|
||||
terminal.error(
|
||||
"Only .msg, .txt, and .lit files are viewable with cat (filename must end with .msg, .txt, or .lit)",
|
||||
"Only .msg, .txt, .lit, .script, .js, and .ns files are viewable with cat (filename must end with .msg, .txt, .lit, .script, .js, or .ns)",
|
||||
);
|
||||
return;
|
||||
}
|
||||
@ -39,11 +41,17 @@ export function cat(
|
||||
}
|
||||
}
|
||||
} else if (filename.endsWith(".txt")) {
|
||||
const txt = terminal.getTextFile(player, filename);
|
||||
const txt = terminal.getTextFile(player, relative_filename);
|
||||
if (txt != null) {
|
||||
txt.show();
|
||||
return;
|
||||
}
|
||||
} else if (filename.endsWith(".script") || filename.endsWith(".js") || filename.endsWith(".ns")) {
|
||||
const script = terminal.getScript(player, relative_filename);
|
||||
if (script != null) {
|
||||
dialogBoxCreate(`${script.filename}<br /><br />${script.code}`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
terminal.error(`No such file ${filename}`);
|
||||
|
@ -3,7 +3,7 @@ import { IRouter } from "../../ui/Router";
|
||||
import { IPlayer } from "../../PersonObjects/IPlayer";
|
||||
import { BaseServer } from "../../Server/BaseServer";
|
||||
import { findRunningScript } from "../../Script/ScriptHelpers";
|
||||
import { isScriptFilename } from "../../Script/isScriptFilename";
|
||||
import { isScriptFilename, validScriptExtensions } from "../../Script/isScriptFilename";
|
||||
|
||||
export function check(
|
||||
terminal: ITerminal,
|
||||
@ -13,19 +13,21 @@ export function check(
|
||||
args: (string | number | boolean)[],
|
||||
): void {
|
||||
if (args.length < 1) {
|
||||
terminal.error("Incorrect number of arguments. Usage: check [script] [arg1] [arg2]...");
|
||||
terminal.error(`Incorrect number of arguments. Usage: check [script] [arg1] [arg2]...`);
|
||||
} else {
|
||||
const scriptName = terminal.getFilepath(args[0] + "");
|
||||
// Can only tail script files
|
||||
if (!isScriptFilename(scriptName)) {
|
||||
terminal.error("tail can only be called on .script files (filename must end with .script)");
|
||||
terminal.error(
|
||||
`'check' can only be called on scripts files (filename must end with ${validScriptExtensions.join(", ")})`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check that the script exists on this machine
|
||||
// Check that the script is running on this machine
|
||||
const runningScript = findRunningScript(scriptName, args.slice(1), server);
|
||||
if (runningScript == null) {
|
||||
terminal.error("No such script exists");
|
||||
terminal.error(`No script named ${scriptName} is running on the server`);
|
||||
return;
|
||||
}
|
||||
runningScript.displayLog();
|
||||
|
@ -24,7 +24,7 @@ export function download(
|
||||
const matchEnding = fn.length == 1 || fn === "*.*" ? null : fn.slice(1); // Treat *.* the same as *
|
||||
const zip = new JSZip();
|
||||
// Helper function to zip any file contents whose name matches the pattern
|
||||
const zipFiles = (fileNames: string[], fileContents: string[]) => {
|
||||
const zipFiles = (fileNames: string[], fileContents: string[]): void => {
|
||||
for (let i = 0; i < fileContents.length; ++i) {
|
||||
let name = fileNames[i];
|
||||
if (name.startsWith("/")) name = name.slice(1);
|
||||
|
@ -27,7 +27,7 @@ export function kill(
|
||||
if (res) {
|
||||
terminal.print(`Killing script with PID ${pid}`);
|
||||
} else {
|
||||
terminal.error(`Failed to kill script with PID ${pid}. No such script exists`);
|
||||
terminal.error(`Failed to kill script with PID ${pid}. No such script is running`);
|
||||
}
|
||||
|
||||
return;
|
||||
|
@ -17,7 +17,7 @@ export function ls(
|
||||
terminal.error("Incorrect usage of ls command. Usage: ls [dir] [| grep pattern]");
|
||||
}
|
||||
|
||||
if (numArgs > 5 || numArgs === 3) {
|
||||
if (numArgs > 4 || numArgs === 2) {
|
||||
return incorrectUsage();
|
||||
}
|
||||
|
||||
@ -30,12 +30,12 @@ export function ls(
|
||||
prefix += "/";
|
||||
}
|
||||
|
||||
// If there are 4+ arguments, then the last 3 must be for grep
|
||||
if (numArgs >= 4) {
|
||||
if (args[numArgs - 1] !== "grep" || args[numArgs - 2] !== "|") {
|
||||
// If there are 3+ arguments, then the last 3 must be for grep
|
||||
if (numArgs >= 3) {
|
||||
if (args[numArgs - 2] !== "grep" || args[numArgs - 3] !== "|") {
|
||||
return incorrectUsage();
|
||||
}
|
||||
filter = args[numArgs] + "";
|
||||
filter = args[numArgs - 1] + "";
|
||||
}
|
||||
|
||||
// If the second argument is not a pipe, then it must be for listing a directory
|
||||
|
@ -29,7 +29,7 @@ export function mem(
|
||||
|
||||
const script = terminal.getScript(player, scriptName);
|
||||
if (script == null) {
|
||||
terminal.error("No such script exists!");
|
||||
terminal.error("mem failed. No such script exists!");
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -33,7 +33,7 @@ export function runProgram(
|
||||
}
|
||||
|
||||
for (const program of Object.values(Programs)) {
|
||||
if (program.name === programName) {
|
||||
if (program.name.toLocaleLowerCase() === programName.toLocaleLowerCase()) {
|
||||
program.run(
|
||||
router,
|
||||
terminal,
|
||||
|
@ -90,7 +90,7 @@ export function scp(
|
||||
}
|
||||
}
|
||||
if (sourceScript == null) {
|
||||
terminal.error("scp() failed. No such script exists");
|
||||
terminal.error("scp failed. No such script exists");
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -3,7 +3,7 @@ import { IRouter } from "../../ui/Router";
|
||||
import { IPlayer } from "../../PersonObjects/IPlayer";
|
||||
import { BaseServer } from "../../Server/BaseServer";
|
||||
import { findRunningScriptByPid } from "../../Script/ScriptHelpers";
|
||||
import { isScriptFilename } from "../../Script/isScriptFilename";
|
||||
import { isScriptFilename, validScriptExtensions } from "../../Script/isScriptFilename";
|
||||
import { compareArrays } from "../../utils/helpers/compareArrays";
|
||||
import { LogBoxEvents } from "../../ui/React/LogBoxManager";
|
||||
|
||||
@ -20,7 +20,7 @@ export function tail(
|
||||
} else if (typeof commandArray[0] === "string") {
|
||||
const scriptName = terminal.getFilepath(commandArray[0]);
|
||||
if (!isScriptFilename(scriptName)) {
|
||||
terminal.error("tail can only be called on .script, .ns, .js files, or by pid");
|
||||
terminal.error(`tail can only be called on ${validScriptExtensions.join(", ")} files, or by PID`);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -66,11 +66,11 @@ export function tail(
|
||||
}
|
||||
|
||||
// if there's no candidate then we just don't know.
|
||||
terminal.error("No such script exists.");
|
||||
terminal.error(`No script named ${scriptName} is running on the server`);
|
||||
} else if (typeof commandArray[0] === "number") {
|
||||
const runningScript = findRunningScriptByPid(commandArray[0], server);
|
||||
if (runningScript == null) {
|
||||
terminal.error("No such script exists");
|
||||
terminal.error(`No script with PID ${commandArray[0]} is running on the server`);
|
||||
return;
|
||||
}
|
||||
LogBoxEvents.emit(runningScript);
|
||||
|
@ -353,6 +353,7 @@ export async function determineAllPossibilitiesForTabCompletion(
|
||||
addAllLitFiles();
|
||||
addAllTextFiles();
|
||||
addAllDirectories();
|
||||
addAllScripts();
|
||||
|
||||
return allPos;
|
||||
}
|
||||
|
@ -48,12 +48,26 @@ export function TerminalInput({ terminal, router, player }: IProps): React.React
|
||||
const terminalInput = useRef<HTMLInputElement>(null);
|
||||
|
||||
const [value, setValue] = useState(command);
|
||||
const [postUpdateValue, setPostUpdateValue] = useState<{postUpdate: () => void} | null>()
|
||||
const [possibilities, setPossibilities] = useState<string[]>([]);
|
||||
const classes = useStyles();
|
||||
|
||||
function saveValue(value: string): void {
|
||||
command = value;
|
||||
setValue(value);
|
||||
// Need to run after state updates, for example if we need to move cursor
|
||||
// *after* we modify input
|
||||
useEffect(() => {
|
||||
if (postUpdateValue?.postUpdate) {
|
||||
postUpdateValue.postUpdate();
|
||||
setPostUpdateValue(null);
|
||||
}
|
||||
}, [postUpdateValue])
|
||||
|
||||
function saveValue(newValue: string, postUpdate?: () => void): void {
|
||||
command = newValue;
|
||||
setValue(newValue);
|
||||
|
||||
if (postUpdate) {
|
||||
setPostUpdateValue({postUpdate});
|
||||
}
|
||||
}
|
||||
|
||||
function handleValueChange(event: React.ChangeEvent<HTMLInputElement>): void {
|
||||
@ -76,24 +90,36 @@ export function TerminalInput({ terminal, router, player }: IProps): React.React
|
||||
}
|
||||
break;
|
||||
case "deletewordbefore": // Delete rest of word before the cursor
|
||||
for (let delStart = start - 1; delStart > 0; --delStart) {
|
||||
if (inputText.charAt(delStart) === " ") {
|
||||
saveValue(inputText.substr(0, delStart) + inputText.substr(start));
|
||||
for (let delStart = start - 1; delStart > -2; --delStart) {
|
||||
if ((inputText.charAt(delStart) === " " || delStart === -1) && delStart !== start - 1) {
|
||||
saveValue(inputText.substr(0, delStart + 1) + inputText.substr(start), () => {
|
||||
// Move cursor to correct location
|
||||
// foo bar |baz bum --> foo |baz bum
|
||||
const ref = terminalInput.current;
|
||||
ref?.setSelectionRange(delStart+1, delStart+1);
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "deletewordafter": // Delete rest of word after the cursor
|
||||
case "deletewordafter": // Delete rest of word after the cursor, including trailing space
|
||||
for (let delStart = start + 1; delStart <= value.length + 1; ++delStart) {
|
||||
if (inputText.charAt(delStart) === " ") {
|
||||
saveValue(inputText.substr(0, start) + inputText.substr(delStart));
|
||||
if (inputText.charAt(delStart) === " " || delStart === value.length + 1) {
|
||||
saveValue(inputText.substr(0, start) + inputText.substr(delStart + 1), () => {
|
||||
// Move cursor to correct location
|
||||
// foo bar |baz bum --> foo bar |bum
|
||||
const ref = terminalInput.current;
|
||||
ref?.setSelectionRange(start, start)
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "clearafter": // Deletes everything after cursor
|
||||
saveValue(inputText.substr(0, start));
|
||||
break;
|
||||
case "clearbefore:": // Deleetes everything before cursor
|
||||
case "clearbefore": // Deletes everything before cursor
|
||||
saveValue(inputText.substr(start), () => moveTextCursor("home"));
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -320,11 +346,23 @@ export function TerminalInput({ terminal, router, player }: IProps): React.React
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
if (event.keyCode === KEY.W && event.ctrlKey) {
|
||||
event.preventDefault();
|
||||
modifyInput("deletewordbefore");
|
||||
}
|
||||
|
||||
if (event.keyCode === KEY.U && event.ctrlKey) {
|
||||
event.preventDefault();
|
||||
modifyInput("clearbefore");
|
||||
}
|
||||
|
||||
if (event.keyCode === KEY.K && event.ctrlKey) {
|
||||
event.preventDefault();
|
||||
modifyInput("clearafter");
|
||||
}
|
||||
|
||||
// TODO AFTER THIS:
|
||||
// alt + d deletes word after cursor
|
||||
// ^w deletes word before cursor
|
||||
// ^k clears line after cursor
|
||||
// ^u clears line before cursor
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { dialogBoxCreate } from "./ui/React/DialogBox";
|
||||
import { BaseServer } from "./Server/BaseServer";
|
||||
import { Generic_fromJSON, Generic_toJSON, Reviver } from "./utils/JSONReviver";
|
||||
import { removeLeadingSlash, isInRootDirectory } from "./Terminal/DirectoryHelpers"
|
||||
|
||||
/**
|
||||
* Represents a plain text file that is typically stored on a server.
|
||||
@ -101,7 +102,11 @@ Reviver.constructors.TextFile = TextFile;
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
export function getTextFile(fn: string, server: BaseServer): TextFile | null {
|
||||
const filename: string = !fn.endsWith(".txt") ? `${fn}.txt` : fn;
|
||||
let filename: string = !fn.endsWith(".txt") ? `${fn}.txt` : fn;
|
||||
|
||||
if (isInRootDirectory(filename)) {
|
||||
filename = removeLeadingSlash(filename);
|
||||
}
|
||||
|
||||
for (const file of server.textFiles as TextFile[]) {
|
||||
if (file.fn === filename) {
|
||||
|
@ -108,7 +108,7 @@ export function TutorialRoot(props: IProps): React.ReactElement {
|
||||
target="_blank"
|
||||
href="https://bitburner.readthedocs.io/en/latest/netscript/netscriptlearntoprogram.html#netscript-1-0-vs-netscript-2-0"
|
||||
>
|
||||
<Typography>NS1 vs NS1 (or .script vs .js)</Typography>
|
||||
<Typography>NS1 vs NS2 (or .script vs .js)</Typography>
|
||||
</Link>
|
||||
</Box>
|
||||
</>
|
||||
|
@ -148,7 +148,7 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [
|
||||
{
|
||||
desc: (n: number[][]): string => {
|
||||
let d: string = [
|
||||
"Given the following array of array of numbers representing a 2D matrix,",
|
||||
"Given the following array of arrays of numbers representing a 2D matrix,",
|
||||
"return the elements of the matrix as an array in spiral order:\n\n",
|
||||
].join(" ");
|
||||
// for (const line of n) {
|
||||
|
@ -319,7 +319,7 @@ export function GameRoot({ player, engine, terminal }: IProps): React.ReactEleme
|
||||
) : (
|
||||
<Box display="flex" flexDirection="row" width="100%">
|
||||
<SidebarRoot player={player} router={Router} page={page} />
|
||||
<Box className={classes.root} flexGrow={1} display="block" px={1} height="100vh">
|
||||
<Box className={classes.root} flexGrow={1} display="block" px={1} min-height="100vh">
|
||||
{page === Page.Terminal ? (
|
||||
<TerminalRoot terminal={terminal} router={Router} player={player} />
|
||||
) : page === Page.Sleeves ? (
|
||||
|
@ -1,7 +1,7 @@
|
||||
// Root React Component for the Corporation UI
|
||||
import React, { useState, useEffect } from "react";
|
||||
|
||||
import { Theme } from "@mui/material/styles";
|
||||
import { Theme, useTheme } from "@mui/material/styles";
|
||||
import makeStyles from "@mui/styles/makeStyles";
|
||||
import createStyles from "@mui/styles/createStyles";
|
||||
import { numeralWrapper } from "../../ui/numeralFormat";
|
||||
@ -20,6 +20,8 @@ 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";
|
||||
|
||||
interface IProps {
|
||||
save: () => void;
|
||||
@ -139,6 +141,8 @@ const useStyles = makeStyles((theme: Theme) =>
|
||||
}),
|
||||
);
|
||||
|
||||
export { useStyles as characterOverviewStyles };
|
||||
|
||||
export function CharacterOverview({ save, killScripts }: IProps): React.ReactElement {
|
||||
const [killOpen, setKillOpen] = useState(false);
|
||||
const player = use.Player();
|
||||
@ -151,6 +155,21 @@ export function CharacterOverview({ save, killScripts }: IProps): React.ReactEle
|
||||
}, []);
|
||||
|
||||
const classes = useStyles();
|
||||
const theme = useTheme();
|
||||
|
||||
const hackingProgress = player.calculateSkillProgress(
|
||||
player.hacking_exp, player.hacking_mult * BitNodeMultipliers.HackingLevelMultiplier);
|
||||
const strengthProgress = player.calculateSkillProgress(
|
||||
player.strength_exp, player.strength_mult * BitNodeMultipliers.StrengthLevelMultiplier);
|
||||
const defenseProgress = player.calculateSkillProgress(
|
||||
player.defense_exp, player.defense_mult * BitNodeMultipliers.DefenseLevelMultiplier);
|
||||
const dexterityProgress = player.calculateSkillProgress(
|
||||
player.dexterity_exp, player.dexterity_mult * BitNodeMultipliers.DexterityLevelMultiplier);
|
||||
const agilityProgress = player.calculateSkillProgress(
|
||||
player.agility_exp, player.agility_mult * BitNodeMultipliers.AgilityLevelMultiplier);
|
||||
const charismaProgress = player.calculateSkillProgress(
|
||||
player.charisma_exp, player.charisma_mult * BitNodeMultipliers.CharismaLevelMultiplier);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Table sx={{ display: "block", m: 1 }}>
|
||||
@ -186,12 +205,20 @@ export function CharacterOverview({ save, killScripts }: IProps): React.ReactEle
|
||||
</TableRow>
|
||||
|
||||
<TableRow>
|
||||
<TableCell component="th" scope="row" classes={{ root: classes.cell }}>
|
||||
<TableCell component="th" scope="row" classes={{ root: classes.cellNone }}>
|
||||
<Typography classes={{ root: classes.hack }}>Hack </Typography>
|
||||
</TableCell>
|
||||
<TableCell align="right" classes={{ root: classes.cell }}>
|
||||
<TableCell align="right" classes={{ root: classes.cellNone }}>
|
||||
<Typography classes={{ root: classes.hack }}>{numeralWrapper.formatSkill(player.hacking)}</Typography>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<StatsProgressOverviewCell progress={hackingProgress} color={theme.colors.hack} />
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell component="th" scope="row" classes={{ root: classes.cell }}>
|
||||
<Typography classes={{ root: classes.hack }}></Typography>
|
||||
</TableCell>
|
||||
<TableCell align="right" classes={{ root: classes.cell }}>
|
||||
<Typography id="overview-hack-hook" classes={{ root: classes.hack }}>
|
||||
{/*Hook for player scripts*/}
|
||||
@ -212,6 +239,9 @@ export function CharacterOverview({ save, killScripts }: IProps): React.ReactEle
|
||||
</Typography>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<StatsProgressOverviewCell progress={strengthProgress} color={theme.colors.combat} />
|
||||
</TableRow>
|
||||
|
||||
<TableRow>
|
||||
<TableCell component="th" scope="row" classes={{ root: classes.cellNone }}>
|
||||
@ -226,6 +256,9 @@ export function CharacterOverview({ save, killScripts }: IProps): React.ReactEle
|
||||
</Typography>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<StatsProgressOverviewCell progress={defenseProgress} color={theme.colors.combat} />
|
||||
</TableRow>
|
||||
|
||||
<TableRow>
|
||||
<TableCell component="th" scope="row" classes={{ root: classes.cellNone }}>
|
||||
@ -240,6 +273,10 @@ export function CharacterOverview({ save, killScripts }: IProps): React.ReactEle
|
||||
</Typography>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<StatsProgressOverviewCell progress={dexterityProgress} color={theme.colors.combat} />
|
||||
</TableRow>
|
||||
|
||||
<TableRow>
|
||||
<TableCell component="th" scope="row" classes={{ root: classes.cell }}>
|
||||
<Typography classes={{ root: classes.combat }}>Agi </Typography>
|
||||
@ -253,6 +290,9 @@ export function CharacterOverview({ save, killScripts }: IProps): React.ReactEle
|
||||
</Typography>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<StatsProgressOverviewCell progress={agilityProgress} color={theme.colors.combat} />
|
||||
</TableRow>
|
||||
|
||||
<TableRow>
|
||||
<TableCell component="th" scope="row" classes={{ root: classes.cellNone }}>
|
||||
@ -267,6 +307,10 @@ export function CharacterOverview({ save, killScripts }: IProps): React.ReactEle
|
||||
</Typography>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<StatsProgressOverviewCell progress={charismaProgress} color={theme.colors.cha} />
|
||||
</TableRow>
|
||||
|
||||
<Intelligence />
|
||||
|
||||
<TableRow>
|
||||
|
@ -8,7 +8,7 @@ interface IProps {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
onConfirm: () => void;
|
||||
confirmationText: string;
|
||||
confirmationText: string | React.ReactNode;
|
||||
}
|
||||
|
||||
export function ConfirmationModal(props: IProps): React.ReactElement {
|
||||
|
@ -70,6 +70,7 @@ export function GameOptionsRoot(props: IProps): React.ReactElement {
|
||||
);
|
||||
const [suppressTIXPopup, setSuppressTIXPopup] = useState(Settings.SuppressTIXPopup);
|
||||
const [suppressBladeburnerPopup, setSuppressBladeburnerPopup] = useState(Settings.SuppressBladeburnerPopup);
|
||||
const [suppressSavedGameToast, setSuppresSavedGameToast] = useState(Settings.SuppressSavedGameToast);
|
||||
|
||||
const [disableHotkeys, setDisableHotkeys] = useState(Settings.DisableHotkeys);
|
||||
const [disableASCIIArt, setDisableASCIIArt] = useState(Settings.DisableASCIIArt);
|
||||
@ -82,6 +83,7 @@ export function GameOptionsRoot(props: IProps): React.ReactElement {
|
||||
const [diagnosticOpen, setDiagnosticOpen] = useState(false);
|
||||
const [deleteGameOpen, setDeleteOpen] = useState(false);
|
||||
const [themeEditorOpen, setThemeEditorOpen] = useState(false);
|
||||
const [softResetOpen, setSoftResetOpen] = useState(false);
|
||||
|
||||
function handleExecTimeChange(event: any, newValue: number | number[]): void {
|
||||
setExecTime(newValue as number);
|
||||
@ -138,6 +140,11 @@ export function GameOptionsRoot(props: IProps): React.ReactElement {
|
||||
Settings.SuppressBladeburnerPopup = event.target.checked;
|
||||
}
|
||||
|
||||
function handleSuppressSavedGameToastChange(event: React.ChangeEvent<HTMLInputElement>): void {
|
||||
setSuppresSavedGameToast(event.target.checked);
|
||||
Settings.SuppressSavedGameToast = event.target.checked;
|
||||
}
|
||||
|
||||
function handleDisableHotkeysChange(event: React.ChangeEvent<HTMLInputElement>): void {
|
||||
setDisableHotkeys(event.target.checked);
|
||||
Settings.DisableHotkeys = event.target.checked;
|
||||
@ -204,6 +211,14 @@ export function GameOptionsRoot(props: IProps): React.ReactElement {
|
||||
reader.readAsText(file);
|
||||
}
|
||||
|
||||
function doSoftReset(): void {
|
||||
if (!Settings.SuppressBuyAugmentationConfirmation) {
|
||||
setSoftResetOpen(true);
|
||||
} else {
|
||||
props.softReset();
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={classes.root} style={{ width: "90%" }}>
|
||||
<Typography variant="h4" gutterBottom>
|
||||
@ -382,7 +397,7 @@ export function GameOptionsRoot(props: IProps): React.ReactElement {
|
||||
</Typography>
|
||||
}
|
||||
>
|
||||
<Typography>Suppress buy augmentation confirmation</Typography>
|
||||
<Typography>Suppress augmentations confirmation</Typography>
|
||||
</Tooltip>
|
||||
}
|
||||
/>
|
||||
@ -420,6 +435,20 @@ export function GameOptionsRoot(props: IProps): React.ReactElement {
|
||||
/>
|
||||
</ListItem>
|
||||
)}
|
||||
<ListItem>
|
||||
<FormControlLabel
|
||||
control={<Switch checked={suppressSavedGameToast} onChange={handleSuppressSavedGameToastChange} />}
|
||||
label={
|
||||
<Tooltip
|
||||
title={
|
||||
<Typography>If this is set, there will be no "Saved Game" toast appearing after save.</Typography>
|
||||
}
|
||||
>
|
||||
<Typography>Suppress Saved Game Toast</Typography>
|
||||
</Tooltip>
|
||||
}
|
||||
/>
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<FormControlLabel
|
||||
control={<Switch checked={disableHotkeys} onChange={handleDisableHotkeysChange} />}
|
||||
@ -626,8 +655,14 @@ export function GameOptionsRoot(props: IProps): React.ReactElement {
|
||||
</Typography>
|
||||
}
|
||||
>
|
||||
<Button onClick={() => props.softReset()}>Soft Reset</Button>
|
||||
<Button onClick={doSoftReset}>Soft Reset</Button>
|
||||
</Tooltip>
|
||||
<ConfirmationModal
|
||||
open={softResetOpen}
|
||||
onClose={() => setSoftResetOpen(false)}
|
||||
onConfirm={props.softReset}
|
||||
confirmationText={"This will perform the same action as installing Augmentations, are you sure?"}
|
||||
/>
|
||||
</Box>
|
||||
<Box>
|
||||
<Tooltip
|
||||
|
@ -43,8 +43,10 @@ export function PromptManager(): React.ReactElement {
|
||||
{prompt != null && (
|
||||
<Modal open={true} onClose={close}>
|
||||
<Typography>{prompt.txt}</Typography>
|
||||
<Button onClick={yes}>Yes</Button>
|
||||
<Button onClick={no}>No</Button>
|
||||
<div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', paddingTop: '10px' }}>
|
||||
<Button style={{ marginRight: 'auto' }} onClick={yes}>Yes</Button>
|
||||
<Button onClick={no}>No</Button>
|
||||
</div>
|
||||
</Modal>
|
||||
)}
|
||||
</>
|
||||
|
67
src/ui/React/StatsProgressBar.tsx
Normal file
67
src/ui/React/StatsProgressBar.tsx
Normal file
@ -0,0 +1,67 @@
|
||||
import * as React from "react";
|
||||
import LinearProgress from "@mui/material/LinearProgress";
|
||||
import { TableCell, Tooltip, Typography } from "@mui/material";
|
||||
import { characterOverviewStyles } from "./CharacterOverview";
|
||||
import { ISkillProgress } from "src/PersonObjects/formulas/skill";
|
||||
import { numeralWrapper } from "../numeralFormat";
|
||||
|
||||
interface IProgressProps {
|
||||
min: number;
|
||||
max: number;
|
||||
current: number;
|
||||
progress: number;
|
||||
color?: React.CSSProperties["color"];
|
||||
}
|
||||
|
||||
interface IStatsOverviewCellProps {
|
||||
progress: ISkillProgress;
|
||||
color?: React.CSSProperties["color"];
|
||||
}
|
||||
|
||||
export function StatsProgressBar({ min, max, current, progress, color }: IProgressProps): React.ReactElement {
|
||||
const tooltip = (
|
||||
<Typography sx={{ textAlign: 'right' }}>
|
||||
<strong>Progress:</strong>
|
||||
{numeralWrapper.formatExp(current - min)} / {numeralWrapper.formatExp(max - min)}
|
||||
<br />
|
||||
<strong>Remaining:</strong>
|
||||
{numeralWrapper.formatExp(max - current)} ({progress.toFixed(2)}%)
|
||||
</Typography>
|
||||
);
|
||||
|
||||
return (
|
||||
<Tooltip title={tooltip}>
|
||||
<LinearProgress
|
||||
variant="determinate"
|
||||
value={progress}
|
||||
sx={{
|
||||
backgroundColor: "#111111",
|
||||
"& .MuiLinearProgress-bar1Determinate": {
|
||||
backgroundColor: color,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
export function StatsProgressOverviewCell({ progress: skill, color }: IStatsOverviewCellProps): React.ReactElement {
|
||||
const classes = characterOverviewStyles();
|
||||
return (
|
||||
<TableCell
|
||||
component="th"
|
||||
scope="row"
|
||||
colSpan={2}
|
||||
classes={{ root: classes.cellNone }}
|
||||
style={{ paddingBottom: "2px", position: "relative", top: "-3px" }}
|
||||
>
|
||||
<StatsProgressBar
|
||||
min={skill.baseExperience}
|
||||
max={skill.nextExperience}
|
||||
current={skill.experience}
|
||||
progress={skill.progress}
|
||||
color={color}
|
||||
/>
|
||||
</TableCell>
|
||||
);
|
||||
}
|
@ -7,9 +7,11 @@ import Paper from "@mui/material/Paper";
|
||||
import TextField from "@mui/material/TextField";
|
||||
import IconButton from "@mui/material/IconButton";
|
||||
import ReplyIcon from "@mui/icons-material/Reply";
|
||||
import PaletteSharpIcon from "@mui/icons-material/PaletteSharp";
|
||||
import { Color, ColorPicker } from "material-ui-color";
|
||||
import { ThemeEvents } from "./Theme";
|
||||
import { Settings, defaultSettings } from "../../Settings/Settings";
|
||||
import { ITheme, getPredefinedThemes } from "../../Settings/Themes";
|
||||
|
||||
interface IProps {
|
||||
open: boolean;
|
||||
@ -40,6 +42,7 @@ function ColorEditor({ name, onColorChange, color, defaultColor }: IColorEditorP
|
||||
<>
|
||||
<ColorPicker
|
||||
hideTextfield
|
||||
deferred
|
||||
value={color}
|
||||
onChange={(newColor: Color) => onColorChange(name, "#" + newColor.hex)}
|
||||
/>
|
||||
@ -63,11 +66,18 @@ export function ThemeEditorModal(props: IProps): React.ReactElement {
|
||||
...Settings.theme,
|
||||
});
|
||||
|
||||
function resetTheme(): void {
|
||||
setCustomTheme({
|
||||
...defaultSettings.theme,
|
||||
});
|
||||
Object.assign(Settings.theme, defaultSettings.theme);
|
||||
const predefinedThemes = getPredefinedThemes();
|
||||
const themes = predefinedThemes && Object.entries(predefinedThemes)
|
||||
.map(([name, templateTheme]) => (
|
||||
<Button onClick={() => setTemplateTheme(templateTheme)}
|
||||
startIcon={<PaletteSharpIcon />} key={name} sx={{ mr: 1 }}>
|
||||
<Typography>{name}</Typography>
|
||||
</Button>
|
||||
)) || <></>;
|
||||
|
||||
function setTheme(theme: ITheme): void {
|
||||
setCustomTheme(theme);
|
||||
Object.assign(Settings.theme, theme);
|
||||
ThemeEvents.emit();
|
||||
}
|
||||
|
||||
@ -95,250 +105,262 @@ export function ThemeEditorModal(props: IProps): React.ReactElement {
|
||||
ThemeEvents.emit();
|
||||
}
|
||||
|
||||
function setTemplateTheme(theme: ITheme): void {
|
||||
setTheme(theme);
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal open={props.open} onClose={props.onClose}>
|
||||
<Paper>
|
||||
<Tooltip open={true} placement={"top"} title={<Typography>Example tooltip</Typography>}>
|
||||
<Button color="primary">primary button</Button>
|
||||
</Tooltip>
|
||||
<Button color="secondary">secondary button</Button>
|
||||
<Button color="warning">warning button</Button>
|
||||
<Button color="info">info button</Button>
|
||||
<Button color="error">error button</Button>
|
||||
<Button disabled>disabled button</Button>
|
||||
<Typography color="primary">text with primary color</Typography>
|
||||
<Typography color="secondary">text with secondary color</Typography>
|
||||
<Typography color="error">text with error color</Typography>
|
||||
<TextField value={"Text field"} />
|
||||
<Paper sx={{ px: 1, py: 1, my: 1 }}>
|
||||
<Tooltip open={true} placement={"top"} title={<Typography>Example tooltip</Typography>}>
|
||||
<Button color="primary" size="small">primary button</Button>
|
||||
</Tooltip>
|
||||
<Button color="secondary" size="small">secondary button</Button>
|
||||
<Button color="warning" size="small">warning button</Button>
|
||||
<Button color="info" size="small">info button</Button>
|
||||
<Button color="error" size="small">error button</Button>
|
||||
<Button disabled size="small">disabled button</Button>
|
||||
|
||||
<br />
|
||||
<Typography color="primary" variant="caption">text with primary color</Typography>
|
||||
<Typography color="secondary" variant="caption">text with secondary color</Typography>
|
||||
<Typography color="error" variant="caption">text with error color</Typography>
|
||||
|
||||
<br />
|
||||
<TextField value={"Text field"} size="small" />
|
||||
</Paper>
|
||||
<br />
|
||||
<ColorEditor
|
||||
name="primarylight"
|
||||
onColorChange={onColorChange}
|
||||
color={customTheme["primarylight"]}
|
||||
defaultColor={defaultSettings.theme["primarylight"]}
|
||||
/>
|
||||
<ColorEditor
|
||||
name="primary"
|
||||
onColorChange={onColorChange}
|
||||
color={customTheme["primary"]}
|
||||
defaultColor={defaultSettings.theme["primary"]}
|
||||
/>
|
||||
<ColorEditor
|
||||
name="primarydark"
|
||||
onColorChange={onColorChange}
|
||||
color={customTheme["primarydark"]}
|
||||
defaultColor={defaultSettings.theme["primarydark"]}
|
||||
/>
|
||||
|
||||
<br />
|
||||
<Paper sx={{ py: 1, my: 1 }}>
|
||||
<ColorEditor
|
||||
name="primarylight"
|
||||
onColorChange={onColorChange}
|
||||
color={customTheme["primarylight"]}
|
||||
defaultColor={defaultSettings.theme["primarylight"]}
|
||||
/>
|
||||
<ColorEditor
|
||||
name="primary"
|
||||
onColorChange={onColorChange}
|
||||
color={customTheme["primary"]}
|
||||
defaultColor={defaultSettings.theme["primary"]}
|
||||
/>
|
||||
<ColorEditor
|
||||
name="primarydark"
|
||||
onColorChange={onColorChange}
|
||||
color={customTheme["primarydark"]}
|
||||
defaultColor={defaultSettings.theme["primarydark"]}
|
||||
/>
|
||||
|
||||
<ColorEditor
|
||||
name="successlight"
|
||||
onColorChange={onColorChange}
|
||||
color={customTheme["successlight"]}
|
||||
defaultColor={defaultSettings.theme["successlight"]}
|
||||
/>
|
||||
<ColorEditor
|
||||
name="success"
|
||||
onColorChange={onColorChange}
|
||||
color={customTheme["success"]}
|
||||
defaultColor={defaultSettings.theme["success"]}
|
||||
/>
|
||||
<ColorEditor
|
||||
name="successdark"
|
||||
onColorChange={onColorChange}
|
||||
color={customTheme["successdark"]}
|
||||
defaultColor={defaultSettings.theme["successdark"]}
|
||||
/>
|
||||
<br />
|
||||
<ColorEditor
|
||||
name="successlight"
|
||||
onColorChange={onColorChange}
|
||||
color={customTheme["successlight"]}
|
||||
defaultColor={defaultSettings.theme["successlight"]}
|
||||
/>
|
||||
<ColorEditor
|
||||
name="success"
|
||||
onColorChange={onColorChange}
|
||||
color={customTheme["success"]}
|
||||
defaultColor={defaultSettings.theme["success"]}
|
||||
/>
|
||||
<ColorEditor
|
||||
name="successdark"
|
||||
onColorChange={onColorChange}
|
||||
color={customTheme["successdark"]}
|
||||
defaultColor={defaultSettings.theme["successdark"]}
|
||||
/>
|
||||
|
||||
<br />
|
||||
<ColorEditor
|
||||
name="errorlight"
|
||||
onColorChange={onColorChange}
|
||||
color={customTheme["errorlight"]}
|
||||
defaultColor={defaultSettings.theme["errorlight"]}
|
||||
/>
|
||||
<ColorEditor
|
||||
name="error"
|
||||
onColorChange={onColorChange}
|
||||
color={customTheme["error"]}
|
||||
defaultColor={defaultSettings.theme["error"]}
|
||||
/>
|
||||
<ColorEditor
|
||||
name="errordark"
|
||||
onColorChange={onColorChange}
|
||||
color={customTheme["errordark"]}
|
||||
defaultColor={defaultSettings.theme["errordark"]}
|
||||
/>
|
||||
<br />
|
||||
<ColorEditor
|
||||
name="errorlight"
|
||||
onColorChange={onColorChange}
|
||||
color={customTheme["errorlight"]}
|
||||
defaultColor={defaultSettings.theme["errorlight"]}
|
||||
/>
|
||||
<ColorEditor
|
||||
name="error"
|
||||
onColorChange={onColorChange}
|
||||
color={customTheme["error"]}
|
||||
defaultColor={defaultSettings.theme["error"]}
|
||||
/>
|
||||
<ColorEditor
|
||||
name="errordark"
|
||||
onColorChange={onColorChange}
|
||||
color={customTheme["errordark"]}
|
||||
defaultColor={defaultSettings.theme["errordark"]}
|
||||
/>
|
||||
|
||||
<br />
|
||||
<ColorEditor
|
||||
name="secondarylight"
|
||||
onColorChange={onColorChange}
|
||||
color={customTheme["secondarylight"]}
|
||||
defaultColor={defaultSettings.theme["secondarylight"]}
|
||||
/>
|
||||
<ColorEditor
|
||||
name="secondary"
|
||||
onColorChange={onColorChange}
|
||||
color={customTheme["secondary"]}
|
||||
defaultColor={defaultSettings.theme["secondary"]}
|
||||
/>
|
||||
<ColorEditor
|
||||
name="secondarydark"
|
||||
onColorChange={onColorChange}
|
||||
color={customTheme["secondarydark"]}
|
||||
defaultColor={defaultSettings.theme["secondarydark"]}
|
||||
/>
|
||||
<br />
|
||||
<ColorEditor
|
||||
name="secondarylight"
|
||||
onColorChange={onColorChange}
|
||||
color={customTheme["secondarylight"]}
|
||||
defaultColor={defaultSettings.theme["secondarylight"]}
|
||||
/>
|
||||
<ColorEditor
|
||||
name="secondary"
|
||||
onColorChange={onColorChange}
|
||||
color={customTheme["secondary"]}
|
||||
defaultColor={defaultSettings.theme["secondary"]}
|
||||
/>
|
||||
<ColorEditor
|
||||
name="secondarydark"
|
||||
onColorChange={onColorChange}
|
||||
color={customTheme["secondarydark"]}
|
||||
defaultColor={defaultSettings.theme["secondarydark"]}
|
||||
/>
|
||||
|
||||
<br />
|
||||
<ColorEditor
|
||||
name="warninglight"
|
||||
onColorChange={onColorChange}
|
||||
color={customTheme["warninglight"]}
|
||||
defaultColor={defaultSettings.theme["warninglight"]}
|
||||
/>
|
||||
<ColorEditor
|
||||
name="warning"
|
||||
onColorChange={onColorChange}
|
||||
color={customTheme["warning"]}
|
||||
defaultColor={defaultSettings.theme["warning"]}
|
||||
/>
|
||||
<ColorEditor
|
||||
name="warningdark"
|
||||
onColorChange={onColorChange}
|
||||
color={customTheme["warningdark"]}
|
||||
defaultColor={defaultSettings.theme["warningdark"]}
|
||||
/>
|
||||
<br />
|
||||
<ColorEditor
|
||||
name="warninglight"
|
||||
onColorChange={onColorChange}
|
||||
color={customTheme["warninglight"]}
|
||||
defaultColor={defaultSettings.theme["warninglight"]}
|
||||
/>
|
||||
<ColorEditor
|
||||
name="warning"
|
||||
onColorChange={onColorChange}
|
||||
color={customTheme["warning"]}
|
||||
defaultColor={defaultSettings.theme["warning"]}
|
||||
/>
|
||||
<ColorEditor
|
||||
name="warningdark"
|
||||
onColorChange={onColorChange}
|
||||
color={customTheme["warningdark"]}
|
||||
defaultColor={defaultSettings.theme["warningdark"]}
|
||||
/>
|
||||
|
||||
<br />
|
||||
<ColorEditor
|
||||
name="infolight"
|
||||
onColorChange={onColorChange}
|
||||
color={customTheme["infolight"]}
|
||||
defaultColor={defaultSettings.theme["infolight"]}
|
||||
/>
|
||||
<ColorEditor
|
||||
name="info"
|
||||
onColorChange={onColorChange}
|
||||
color={customTheme["info"]}
|
||||
defaultColor={defaultSettings.theme["info"]}
|
||||
/>
|
||||
<ColorEditor
|
||||
name="infodark"
|
||||
onColorChange={onColorChange}
|
||||
color={customTheme["infodark"]}
|
||||
defaultColor={defaultSettings.theme["infodark"]}
|
||||
/>
|
||||
<br />
|
||||
<ColorEditor
|
||||
name="infolight"
|
||||
onColorChange={onColorChange}
|
||||
color={customTheme["infolight"]}
|
||||
defaultColor={defaultSettings.theme["infolight"]}
|
||||
/>
|
||||
<ColorEditor
|
||||
name="info"
|
||||
onColorChange={onColorChange}
|
||||
color={customTheme["info"]}
|
||||
defaultColor={defaultSettings.theme["info"]}
|
||||
/>
|
||||
<ColorEditor
|
||||
name="infodark"
|
||||
onColorChange={onColorChange}
|
||||
color={customTheme["infodark"]}
|
||||
defaultColor={defaultSettings.theme["infodark"]}
|
||||
/>
|
||||
|
||||
<br />
|
||||
<ColorEditor
|
||||
name="welllight"
|
||||
onColorChange={onColorChange}
|
||||
color={customTheme["welllight"]}
|
||||
defaultColor={defaultSettings.theme["welllight"]}
|
||||
/>
|
||||
<ColorEditor
|
||||
name="well"
|
||||
onColorChange={onColorChange}
|
||||
color={customTheme["well"]}
|
||||
defaultColor={defaultSettings.theme["well"]}
|
||||
/>
|
||||
<ColorEditor
|
||||
name="white"
|
||||
onColorChange={onColorChange}
|
||||
color={customTheme["white"]}
|
||||
defaultColor={defaultSettings.theme["white"]}
|
||||
/>
|
||||
<ColorEditor
|
||||
name="black"
|
||||
onColorChange={onColorChange}
|
||||
color={customTheme["black"]}
|
||||
defaultColor={defaultSettings.theme["black"]}
|
||||
/>
|
||||
<ColorEditor
|
||||
name="backgroundprimary"
|
||||
onColorChange={onColorChange}
|
||||
color={customTheme["backgroundprimary"]}
|
||||
defaultColor={defaultSettings.theme["backgroundprimary"]}
|
||||
/>
|
||||
<ColorEditor
|
||||
name="backgroundsecondary"
|
||||
onColorChange={onColorChange}
|
||||
color={customTheme["backgroundsecondary"]}
|
||||
defaultColor={defaultSettings.theme["backgroundsecondary"]}
|
||||
/>
|
||||
<ColorEditor
|
||||
name="button"
|
||||
onColorChange={onColorChange}
|
||||
color={customTheme["button"]}
|
||||
defaultColor={defaultSettings.theme["button"]}
|
||||
/>
|
||||
<br />
|
||||
<ColorEditor
|
||||
name="welllight"
|
||||
onColorChange={onColorChange}
|
||||
color={customTheme["welllight"]}
|
||||
defaultColor={defaultSettings.theme["welllight"]}
|
||||
/>
|
||||
<ColorEditor
|
||||
name="well"
|
||||
onColorChange={onColorChange}
|
||||
color={customTheme["well"]}
|
||||
defaultColor={defaultSettings.theme["well"]}
|
||||
/>
|
||||
<ColorEditor
|
||||
name="white"
|
||||
onColorChange={onColorChange}
|
||||
color={customTheme["white"]}
|
||||
defaultColor={defaultSettings.theme["white"]}
|
||||
/>
|
||||
<ColorEditor
|
||||
name="black"
|
||||
onColorChange={onColorChange}
|
||||
color={customTheme["black"]}
|
||||
defaultColor={defaultSettings.theme["black"]}
|
||||
/>
|
||||
<ColorEditor
|
||||
name="backgroundprimary"
|
||||
onColorChange={onColorChange}
|
||||
color={customTheme["backgroundprimary"]}
|
||||
defaultColor={defaultSettings.theme["backgroundprimary"]}
|
||||
/>
|
||||
<ColorEditor
|
||||
name="backgroundsecondary"
|
||||
onColorChange={onColorChange}
|
||||
color={customTheme["backgroundsecondary"]}
|
||||
defaultColor={defaultSettings.theme["backgroundsecondary"]}
|
||||
/>
|
||||
<ColorEditor
|
||||
name="button"
|
||||
onColorChange={onColorChange}
|
||||
color={customTheme["button"]}
|
||||
defaultColor={defaultSettings.theme["button"]}
|
||||
/>
|
||||
|
||||
<br />
|
||||
<ColorEditor
|
||||
name="hp"
|
||||
onColorChange={onColorChange}
|
||||
color={customTheme["hp"]}
|
||||
defaultColor={defaultSettings.theme["hp"]}
|
||||
/>
|
||||
<ColorEditor
|
||||
name="money"
|
||||
onColorChange={onColorChange}
|
||||
color={customTheme["money"]}
|
||||
defaultColor={defaultSettings.theme["money"]}
|
||||
/>
|
||||
<ColorEditor
|
||||
name="hack"
|
||||
onColorChange={onColorChange}
|
||||
color={customTheme["hack"]}
|
||||
defaultColor={defaultSettings.theme["hack"]}
|
||||
/>
|
||||
<ColorEditor
|
||||
name="combat"
|
||||
onColorChange={onColorChange}
|
||||
color={customTheme["combat"]}
|
||||
defaultColor={defaultSettings.theme["combat"]}
|
||||
/>
|
||||
<ColorEditor
|
||||
name="cha"
|
||||
onColorChange={onColorChange}
|
||||
color={customTheme["cha"]}
|
||||
defaultColor={defaultSettings.theme["cha"]}
|
||||
/>
|
||||
<ColorEditor
|
||||
name="int"
|
||||
onColorChange={onColorChange}
|
||||
color={customTheme["int"]}
|
||||
defaultColor={defaultSettings.theme["int"]}
|
||||
/>
|
||||
<ColorEditor
|
||||
name="rep"
|
||||
onColorChange={onColorChange}
|
||||
color={customTheme["rep"]}
|
||||
defaultColor={defaultSettings.theme["rep"]}
|
||||
/>
|
||||
<ColorEditor
|
||||
name="disabled"
|
||||
onColorChange={onColorChange}
|
||||
color={customTheme["disabled"]}
|
||||
defaultColor={defaultSettings.theme["disabled"]}
|
||||
/>
|
||||
<br />
|
||||
<br />
|
||||
<TextField
|
||||
label={"import / export theme"}
|
||||
value={JSON.stringify(customTheme)}
|
||||
onChange={onThemeChange}
|
||||
InputProps={{
|
||||
endAdornment: (
|
||||
<IconButton onClick={resetTheme} size="large">
|
||||
<ReplyIcon />
|
||||
</IconButton>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<br />
|
||||
<ColorEditor
|
||||
name="hp"
|
||||
onColorChange={onColorChange}
|
||||
color={customTheme["hp"]}
|
||||
defaultColor={defaultSettings.theme["hp"]}
|
||||
/>
|
||||
<ColorEditor
|
||||
name="money"
|
||||
onColorChange={onColorChange}
|
||||
color={customTheme["money"]}
|
||||
defaultColor={defaultSettings.theme["money"]}
|
||||
/>
|
||||
<ColorEditor
|
||||
name="hack"
|
||||
onColorChange={onColorChange}
|
||||
color={customTheme["hack"]}
|
||||
defaultColor={defaultSettings.theme["hack"]}
|
||||
/>
|
||||
<ColorEditor
|
||||
name="combat"
|
||||
onColorChange={onColorChange}
|
||||
color={customTheme["combat"]}
|
||||
defaultColor={defaultSettings.theme["combat"]}
|
||||
/>
|
||||
<ColorEditor
|
||||
name="cha"
|
||||
onColorChange={onColorChange}
|
||||
color={customTheme["cha"]}
|
||||
defaultColor={defaultSettings.theme["cha"]}
|
||||
/>
|
||||
<ColorEditor
|
||||
name="int"
|
||||
onColorChange={onColorChange}
|
||||
color={customTheme["int"]}
|
||||
defaultColor={defaultSettings.theme["int"]}
|
||||
/>
|
||||
<ColorEditor
|
||||
name="rep"
|
||||
onColorChange={onColorChange}
|
||||
color={customTheme["rep"]}
|
||||
defaultColor={defaultSettings.theme["rep"]}
|
||||
/>
|
||||
<ColorEditor
|
||||
name="disabled"
|
||||
onColorChange={onColorChange}
|
||||
color={customTheme["disabled"]}
|
||||
defaultColor={defaultSettings.theme["disabled"]}
|
||||
/>
|
||||
</Paper>
|
||||
|
||||
<Paper sx={{ px: 1, py: 1, my: 1 }}>
|
||||
<TextField
|
||||
sx={{ mb: 1 }}
|
||||
multiline
|
||||
fullWidth
|
||||
maxRows={3}
|
||||
label={"import / export theme"}
|
||||
value={JSON.stringify(customTheme)}
|
||||
onChange={onThemeChange}
|
||||
/>
|
||||
<>
|
||||
<Typography sx={{ my: 1 }}>Backup your theme or share it with others by copying the string above.</Typography>
|
||||
<Typography sx={{ my: 1 }}>Replace the current theme with a pre-built template using the buttons below.</Typography>
|
||||
{themes}
|
||||
</>
|
||||
</Paper>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
@ -1,3 +1,6 @@
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
import { jest, describe, expect, test } from '@jest/globals'
|
||||
|
||||
import { NetscriptFunctions } from "../../src/NetscriptFunctions";
|
||||
import { getRamCost, RamCostConstants } from "../../src/Netscript/RamCostGenerator";
|
||||
import { Environment } from "../../src/Netscript/Environment";
|
||||
@ -5,6 +8,10 @@ import { RunningScript } from "../../src/Script/RunningScript";
|
||||
import { Script } from "../../src/Script/Script";
|
||||
import { SourceFileFlags } from "../../src/SourceFile/SourceFileFlags";
|
||||
|
||||
jest.mock(`!!raw-loader!../NetscriptDefinitions.d.ts`, () => '', {
|
||||
virtual: true,
|
||||
});
|
||||
|
||||
const ScriptBaseCost = RamCostConstants.ScriptBaseRamCost;
|
||||
|
||||
describe("Netscript Dynamic RAM Calculation/Generation Tests", function () {
|
||||
@ -106,12 +113,13 @@ describe("Netscript Dynamic RAM Calculation/Generation Tests", function () {
|
||||
* @param {string[]} fnDesc - describes the name of the function being tested,
|
||||
* including the namespace(s). e.g. ["gang", "getMemberNames"]
|
||||
*/
|
||||
async function testZeroDynamicRamCost(fnDesc) {
|
||||
async function testZeroDynamicRamCost(fnDesc, skipRun = false) {
|
||||
if (!Array.isArray(fnDesc)) {
|
||||
throw new Error("Non-array passed to testZeroDynamicRamCost()");
|
||||
}
|
||||
const expected = getRamCost(...fnDesc);
|
||||
expect(expected).toEqual(0);
|
||||
if (skipRun) return;
|
||||
|
||||
const code = `${fnDesc.join(".")}();`;
|
||||
|
||||
@ -154,7 +162,7 @@ describe("Netscript Dynamic RAM Calculation/Generation Tests", function () {
|
||||
runPotentiallyAsyncFunction(curr);
|
||||
} catch (e) {}
|
||||
} else {
|
||||
throw new Error(`Invalid function specified: [${fndesc}]`);
|
||||
throw new Error(`Invalid function specified: [${fnDesc}]`);
|
||||
}
|
||||
|
||||
testEquality(workerScript.dynamicRamUsage, ScriptBaseCost);
|
||||
@ -188,13 +196,13 @@ describe("Netscript Dynamic RAM Calculation/Generation Tests", function () {
|
||||
await testNonzeroDynamicRamCost(f);
|
||||
});
|
||||
|
||||
it("hackAnalyzePercent()", async function () {
|
||||
const f = ["hackAnalyzePercent"];
|
||||
it("hackAnalyze()", async function () {
|
||||
const f = ["hackAnalyze"];
|
||||
await testNonzeroDynamicRamCost(f);
|
||||
});
|
||||
|
||||
it("hackChance()", async function () {
|
||||
const f = ["hackChance"];
|
||||
it("hackAnalyzeChance()", async function () {
|
||||
const f = ["hackAnalyzeChance"];
|
||||
await testNonzeroDynamicRamCost(f);
|
||||
});
|
||||
|
||||
@ -285,6 +293,7 @@ describe("Netscript Dynamic RAM Calculation/Generation Tests", function () {
|
||||
|
||||
it("exec()", async function () {
|
||||
const f = ["exec"];
|
||||
jest.spyOn(console, 'log').mockImplementation(() => {}); // eslint-disable-line
|
||||
await testNonzeroDynamicRamCost(f);
|
||||
});
|
||||
|
||||
@ -305,7 +314,7 @@ describe("Netscript Dynamic RAM Calculation/Generation Tests", function () {
|
||||
|
||||
it("exit()", async function () {
|
||||
const f = ["exit"];
|
||||
await testZeroDynamicRamCost(f);
|
||||
await testZeroDynamicRamCost(f, true);
|
||||
});
|
||||
|
||||
it("scp()", async function () {
|
||||
@ -450,32 +459,32 @@ describe("Netscript Dynamic RAM Calculation/Generation Tests", function () {
|
||||
|
||||
it("write()", async function () {
|
||||
const f = ["write"];
|
||||
await testNonzeroDynamicRamCost(f);
|
||||
await testZeroDynamicRamCost(f);
|
||||
});
|
||||
|
||||
it("tryWrite()", async function () {
|
||||
const f = ["tryWrite"];
|
||||
await testNonzeroDynamicRamCost(f);
|
||||
it("tryWritePort()", async function () {
|
||||
const f = ["tryWritePort"];
|
||||
await testZeroDynamicRamCost(f);
|
||||
});
|
||||
|
||||
it("read()", async function () {
|
||||
const f = ["read"];
|
||||
await testNonzeroDynamicRamCost(f);
|
||||
await testZeroDynamicRamCost(f);
|
||||
});
|
||||
|
||||
it("peek()", async function () {
|
||||
const f = ["peek"];
|
||||
await testNonzeroDynamicRamCost(f);
|
||||
await testZeroDynamicRamCost(f);
|
||||
});
|
||||
|
||||
it("clear()", async function () {
|
||||
const f = ["clear"];
|
||||
await testNonzeroDynamicRamCost(f);
|
||||
await testZeroDynamicRamCost(f);
|
||||
});
|
||||
|
||||
it("getPortHandle()", async function () {
|
||||
const f = ["getPortHandle"];
|
||||
await testNonzeroDynamicRamCost(f);
|
||||
await testZeroDynamicRamCost(f);
|
||||
});
|
||||
|
||||
it("rm()", async function () {
|
||||
@ -577,88 +586,88 @@ describe("Netscript Dynamic RAM Calculation/Generation Tests", function () {
|
||||
});
|
||||
|
||||
describe("TIX API", function () {
|
||||
it("getStockSymbols()", async function () {
|
||||
const f = ["getStockSymbols"];
|
||||
it("stock.getSymbols()", async function () {
|
||||
const f = ["stock", "getSymbols"];
|
||||
await testNonzeroDynamicRamCost(f);
|
||||
});
|
||||
|
||||
it("getStockPrice()", async function () {
|
||||
const f = ["getStockPrice"];
|
||||
it("stock.getPrice()", async function () {
|
||||
const f = ["stock", "getPrice"];
|
||||
await testNonzeroDynamicRamCost(f);
|
||||
});
|
||||
|
||||
it("getStockAskPrice()", async function () {
|
||||
const f = ["getStockAskPrice"];
|
||||
it("stock.getBidPrice()", async function () {
|
||||
const f = ["stock", "getBidPrice"];
|
||||
await testNonzeroDynamicRamCost(f);
|
||||
});
|
||||
|
||||
it("getStockBidPrice()", async function () {
|
||||
const f = ["getStockBidPrice"];
|
||||
it("stock.getBidPrice()", async function () {
|
||||
const f = ["stock", "getBidPrice"];
|
||||
await testNonzeroDynamicRamCost(f);
|
||||
});
|
||||
|
||||
it("getStockPosition()", async function () {
|
||||
const f = ["getStockPosition"];
|
||||
it("stock.getPosition()", async function () {
|
||||
const f = ["stock", "getPosition"];
|
||||
await testNonzeroDynamicRamCost(f);
|
||||
});
|
||||
|
||||
it("getStockMaxShares()", async function () {
|
||||
const f = ["getStockMaxShares"];
|
||||
it("stock.getMaxShares()", async function () {
|
||||
const f = ["stock", "getMaxShares"];
|
||||
await testNonzeroDynamicRamCost(f);
|
||||
});
|
||||
|
||||
it("buyStock()", async function () {
|
||||
const f = ["buyStock"];
|
||||
it("stock.buy()", async function () {
|
||||
const f = ["stock", "buy"];
|
||||
await testNonzeroDynamicRamCost(f);
|
||||
});
|
||||
|
||||
it("sellStock()", async function () {
|
||||
const f = ["sellStock"];
|
||||
it("stock.sell()", async function () {
|
||||
const f = ["stock", "sell"];
|
||||
await testNonzeroDynamicRamCost(f);
|
||||
});
|
||||
|
||||
it("shortStock()", async function () {
|
||||
const f = ["shortStock"];
|
||||
it("stock.short()", async function () {
|
||||
const f = ["stock", "short"];
|
||||
await testNonzeroDynamicRamCost(f);
|
||||
});
|
||||
|
||||
it("sellShort()", async function () {
|
||||
const f = ["sellShort"];
|
||||
it("stock.sellShort()", async function () {
|
||||
const f = ["stock", "sellShort"];
|
||||
await testNonzeroDynamicRamCost(f);
|
||||
});
|
||||
|
||||
it("placeOrder()", async function () {
|
||||
const f = ["placeOrder"];
|
||||
it("stock.placeOrder()", async function () {
|
||||
const f = ["stock", "placeOrder"];
|
||||
await testNonzeroDynamicRamCost(f);
|
||||
});
|
||||
|
||||
it("cancelOrder()", async function () {
|
||||
const f = ["cancelOrder"];
|
||||
it("stock.cancelOrder()", async function () {
|
||||
const f = ["stock", "cancelOrder"];
|
||||
await testNonzeroDynamicRamCost(f);
|
||||
});
|
||||
|
||||
it("getOrders()", async function () {
|
||||
const f = ["getOrders"];
|
||||
it("stock.getOrders()", async function () {
|
||||
const f = ["stock", "getOrders"];
|
||||
await testNonzeroDynamicRamCost(f);
|
||||
});
|
||||
|
||||
it("getStockVolatility()", async function () {
|
||||
const f = ["getStockVolatility"];
|
||||
it("stock.getVolatility()", async function () {
|
||||
const f = ["stock", "getVolatility"];
|
||||
await testNonzeroDynamicRamCost(f);
|
||||
});
|
||||
|
||||
it("getStockForecast()", async function () {
|
||||
const f = ["getStockForecast"];
|
||||
it("stock.getForecast()", async function () {
|
||||
const f = ["stock", "getForecast"];
|
||||
await testNonzeroDynamicRamCost(f);
|
||||
});
|
||||
|
||||
it("purchase4SMarketData()", async function () {
|
||||
const f = ["purchase4SMarketData"];
|
||||
it("stock.purchase4SMarketData()", async function () {
|
||||
const f = ["stock", "purchase4SMarketData"];
|
||||
await testNonzeroDynamicRamCost(f);
|
||||
});
|
||||
|
||||
it("purchase4SMarketDataTixApi()", async function () {
|
||||
const f = ["purchase4SMarketDataTixApi"];
|
||||
it("stock.purchase4SMarketDataTixApi()", async function () {
|
||||
const f = ["stock", "purchase4SMarketDataTixApi"];
|
||||
await testNonzeroDynamicRamCost(f);
|
||||
});
|
||||
});
|
||||
@ -971,11 +980,6 @@ describe("Netscript Dynamic RAM Calculation/Generation Tests", function () {
|
||||
await testNonzeroDynamicRamCost(f);
|
||||
});
|
||||
|
||||
it("getCityEstimatedCommunities()", async function () {
|
||||
const f = ["bladeburner", "getCityEstimatedCommunities"];
|
||||
await testNonzeroDynamicRamCost(f);
|
||||
});
|
||||
|
||||
it("getCityChaos()", async function () {
|
||||
const f = ["bladeburner", "getCityChaos"];
|
||||
await testNonzeroDynamicRamCost(f);
|
||||
|
@ -1,3 +1,6 @@
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
import { jest, describe, expect, test } from '@jest/globals'
|
||||
|
||||
import { getRamCost, RamCostConstants } from "../../src/Netscript/RamCostGenerator";
|
||||
import { calculateRamUsage } from "../../src/Script/RamCalculations";
|
||||
|
||||
@ -81,13 +84,13 @@ describe("Netscript Static RAM Calculation/Generation Tests", function () {
|
||||
await expectNonZeroRamCost(f);
|
||||
});
|
||||
|
||||
it("hackAnalyzePercent()", async function () {
|
||||
const f = ["hackAnalyzePercent"];
|
||||
it("hackAnalyze()", async function () {
|
||||
const f = ["hackAnalyze"];
|
||||
await expectNonZeroRamCost(f);
|
||||
});
|
||||
|
||||
it("hackChance()", async function () {
|
||||
const f = ["hackChance"];
|
||||
it("hackAnalyzeChance()", async function () {
|
||||
const f = ["hackAnalyzeChance"];
|
||||
await expectNonZeroRamCost(f);
|
||||
});
|
||||
|
||||
@ -343,32 +346,32 @@ describe("Netscript Static RAM Calculation/Generation Tests", function () {
|
||||
|
||||
it("write()", async function () {
|
||||
const f = ["write"];
|
||||
await expectNonZeroRamCost(f);
|
||||
await expectZeroRamCost(f);
|
||||
});
|
||||
|
||||
it("tryWrite()", async function () {
|
||||
const f = ["tryWrite"];
|
||||
await expectNonZeroRamCost(f);
|
||||
it("tryWritePort()", async function () {
|
||||
const f = ["tryWritePort"];
|
||||
await expectZeroRamCost(f);
|
||||
});
|
||||
|
||||
it("read()", async function () {
|
||||
const f = ["read"];
|
||||
await expectNonZeroRamCost(f);
|
||||
await expectZeroRamCost(f);
|
||||
});
|
||||
|
||||
it("peek()", async function () {
|
||||
const f = ["peek"];
|
||||
await expectNonZeroRamCost(f);
|
||||
await expectZeroRamCost(f);
|
||||
});
|
||||
|
||||
it("clear()", async function () {
|
||||
const f = ["clear"];
|
||||
await expectNonZeroRamCost(f);
|
||||
await expectZeroRamCost(f);
|
||||
});
|
||||
|
||||
it("getPortHandle()", async function () {
|
||||
const f = ["getPortHandle"];
|
||||
await expectNonZeroRamCost(f);
|
||||
await expectZeroRamCost(f);
|
||||
});
|
||||
|
||||
it("rm()", async function () {
|
||||
@ -508,98 +511,98 @@ describe("Netscript Static RAM Calculation/Generation Tests", function () {
|
||||
});
|
||||
|
||||
describe("TIX API", function () {
|
||||
it("getStockSymbols()", async function () {
|
||||
const f = ["getStockSymbols"];
|
||||
it("stock.getSymbols()", async function () {
|
||||
const f = ["stock", "getSymbols"];
|
||||
await expectNonZeroRamCost(f);
|
||||
});
|
||||
|
||||
it("getStockPrice()", async function () {
|
||||
const f = ["getStockPrice"];
|
||||
it("stock.getPrice()", async function () {
|
||||
const f = ["stock", "getPrice"];
|
||||
await expectNonZeroRamCost(f);
|
||||
});
|
||||
|
||||
it("getStockAskPrice()", async function () {
|
||||
const f = ["getStockAskPrice"];
|
||||
it("stock.getAskPrice()", async function () {
|
||||
const f = ["stock", "getAskPrice"];
|
||||
await expectNonZeroRamCost(f);
|
||||
});
|
||||
|
||||
it("getStockBidPrice()", async function () {
|
||||
const f = ["getStockBidPrice"];
|
||||
it("stock.getBidPrice()", async function () {
|
||||
const f = ["stock", "getBidPrice"];
|
||||
await expectNonZeroRamCost(f);
|
||||
});
|
||||
|
||||
it("getStockPosition()", async function () {
|
||||
const f = ["getStockPosition"];
|
||||
it("stock.getPosition()", async function () {
|
||||
const f = ["stock", "getPosition"];
|
||||
await expectNonZeroRamCost(f);
|
||||
});
|
||||
|
||||
it("getStockMaxShares()", async function () {
|
||||
const f = ["getStockMaxShares"];
|
||||
it("stock.getMaxShares()", async function () {
|
||||
const f = ["stock", "getMaxShares"];
|
||||
await expectNonZeroRamCost(f);
|
||||
});
|
||||
|
||||
it("getStockPurchaseCost()", async function () {
|
||||
const f = ["getStockPurchaseCost"];
|
||||
it("stock.getPurchaseCost()", async function () {
|
||||
const f = ["stock", "getPurchaseCost"];
|
||||
await expectNonZeroRamCost(f);
|
||||
});
|
||||
|
||||
it("getStockSaleGain()", async function () {
|
||||
const f = ["getStockSaleGain"];
|
||||
it("stock.getSaleGain()", async function () {
|
||||
const f = ["stock", "getSaleGain"];
|
||||
await expectNonZeroRamCost(f);
|
||||
});
|
||||
|
||||
it("buyStock()", async function () {
|
||||
const f = ["buyStock"];
|
||||
it("stock.buy()", async function () {
|
||||
const f = ["stock", "buy"];
|
||||
await expectNonZeroRamCost(f);
|
||||
});
|
||||
|
||||
it("sellStock()", async function () {
|
||||
const f = ["sellStock"];
|
||||
it("stock.sell()", async function () {
|
||||
const f = ["stock", "sell"];
|
||||
await expectNonZeroRamCost(f);
|
||||
});
|
||||
|
||||
it("shortStock()", async function () {
|
||||
const f = ["shortStock"];
|
||||
it("stock.short()", async function () {
|
||||
const f = ["stock", "short"];
|
||||
await expectNonZeroRamCost(f);
|
||||
});
|
||||
|
||||
it("sellShort()", async function () {
|
||||
const f = ["sellShort"];
|
||||
it("stock.sellShort()", async function () {
|
||||
const f = ["stock", "sell"];
|
||||
await expectNonZeroRamCost(f);
|
||||
});
|
||||
|
||||
it("placeOrder()", async function () {
|
||||
const f = ["placeOrder"];
|
||||
it("stock.placeOrder()", async function () {
|
||||
const f = ["stock", "placeOrder"];
|
||||
await expectNonZeroRamCost(f);
|
||||
});
|
||||
|
||||
it("cancelOrder()", async function () {
|
||||
const f = ["cancelOrder"];
|
||||
it("stock.cancelOrder()", async function () {
|
||||
const f = ["stock", "cancelOrder"];
|
||||
await expectNonZeroRamCost(f);
|
||||
});
|
||||
|
||||
it("getOrders()", async function () {
|
||||
const f = ["getOrders"];
|
||||
it("stock.getOrders()", async function () {
|
||||
const f = ["stock", "getOrders"];
|
||||
await expectNonZeroRamCost(f);
|
||||
});
|
||||
|
||||
it("getStockVolatility()", async function () {
|
||||
const f = ["getStockVolatility"];
|
||||
it("stock.getVolatility()", async function () {
|
||||
const f = ["stock", "getVolatility"];
|
||||
await expectNonZeroRamCost(f);
|
||||
});
|
||||
|
||||
it("getStockForecast()", async function () {
|
||||
const f = ["getStockForecast"];
|
||||
it("stock.getForecast()", async function () {
|
||||
const f = ["stock", "getForecast"];
|
||||
await expectNonZeroRamCost(f);
|
||||
});
|
||||
|
||||
it("purchase4SMarketData()", async function () {
|
||||
const f = ["purchase4SMarketData"];
|
||||
it("stock.purchase4SMarketData()", async function () {
|
||||
const f = ["stock", "purchase4SMarketData"];
|
||||
await expectNonZeroRamCost(f);
|
||||
});
|
||||
|
||||
it("purchase4SMarketDataTixApi()", async function () {
|
||||
const f = ["purchase4SMarketDataTixApi"];
|
||||
it("stock.purchase4SMarketDataTixApi()", async function () {
|
||||
const f = ["stock", "purchase4SMarketDataTixApi"];
|
||||
await expectNonZeroRamCost(f);
|
||||
});
|
||||
});
|
||||
@ -912,11 +915,6 @@ describe("Netscript Static RAM Calculation/Generation Tests", function () {
|
||||
await expectNonZeroRamCost(f);
|
||||
});
|
||||
|
||||
it("getCityEstimatedCommunities()", async function () {
|
||||
const f = ["bladeburner", "getCityEstimatedCommunities"];
|
||||
await expectNonZeroRamCost(f);
|
||||
});
|
||||
|
||||
it("getCityChaos()", async function () {
|
||||
const f = ["bladeburner", "getCityChaos"];
|
||||
await expectNonZeroRamCost(f);
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,19 +1,21 @@
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
import { jest, describe, expect, test } from '@jest/globals'
|
||||
import { convertTimeMsToTimeElapsedString } from "../src/utils/StringHelperFunctions";
|
||||
|
||||
describe("StringHelperFunctions Tests", function () {
|
||||
it("transforms strings", () => {
|
||||
expect(convertTimeMsToTimeElapsedString(1000)).equal("1 seconds");
|
||||
expect(convertTimeMsToTimeElapsedString(5 * 60 * 1000 + 34 * 1000)).equal("5 minutes 34 seconds");
|
||||
expect(convertTimeMsToTimeElapsedString(2 * 60 * 60 * 24 * 1000 + 5 * 60 * 1000 + 34 * 1000)).equal(
|
||||
expect(convertTimeMsToTimeElapsedString(1000)).toEqual("1 seconds");
|
||||
expect(convertTimeMsToTimeElapsedString(5 * 60 * 1000 + 34 * 1000)).toEqual("5 minutes 34 seconds");
|
||||
expect(convertTimeMsToTimeElapsedString(2 * 60 * 60 * 24 * 1000 + 5 * 60 * 1000 + 34 * 1000)).toEqual(
|
||||
"2 days 5 minutes 34 seconds",
|
||||
);
|
||||
expect(convertTimeMsToTimeElapsedString(2 * 60 * 60 * 24 * 1000 + 5 * 60 * 1000 + 34 * 1000, true)).equal(
|
||||
expect(convertTimeMsToTimeElapsedString(2 * 60 * 60 * 24 * 1000 + 5 * 60 * 1000 + 34 * 1000, true)).toEqual(
|
||||
"2 days 5 minutes 34.000 seconds",
|
||||
);
|
||||
expect(convertTimeMsToTimeElapsedString(2 * 60 * 60 * 24 * 1000 + 5 * 60 * 1000 + 34 * 1000 + 123, true)).equal(
|
||||
expect(convertTimeMsToTimeElapsedString(2 * 60 * 60 * 24 * 1000 + 5 * 60 * 1000 + 34 * 1000 + 123, true)).toEqual(
|
||||
"2 days 5 minutes 34.123 seconds",
|
||||
);
|
||||
expect(convertTimeMsToTimeElapsedString(2 * 60 * 60 * 24 * 1000 + 5 * 60 * 1000 + 34 * 1000 + 123.888, true)).equal(
|
||||
expect(convertTimeMsToTimeElapsedString(2 * 60 * 60 * 24 * 1000 + 5 * 60 * 1000 + 34 * 1000 + 123.888, true)).toEqual(
|
||||
"2 days 5 minutes 34.123 seconds",
|
||||
);
|
||||
});
|
||||
|
@ -1,3 +1,5 @@
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
import { jest, describe, expect, test } from '@jest/globals'
|
||||
import * as dirHelpers from "../../src/Terminal/DirectoryHelpers";
|
||||
|
||||
describe("Terminal Directory Tests", function () {
|
||||
|
@ -1,3 +1,7 @@
|
||||
/* eslint-disable no-await-in-loop */
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
import { jest, describe, expect, test } from '@jest/globals'
|
||||
|
||||
import { Player } from "../../src/Player";
|
||||
import { determineAllPossibilitiesForTabCompletion } from "../../src/Terminal/determineAllPossibilitiesForTabCompletion";
|
||||
import { Server } from "../../src/Server/Server";
|
||||
@ -5,6 +9,10 @@ import { AddToAllServers, prestigeAllServers } from "../../src/Server/AllServers
|
||||
import { LocationName } from "../../src/Locations/data/LocationNames";
|
||||
import { CodingContract } from "../../src/CodingContracts";
|
||||
|
||||
jest.mock(`!!raw-loader!../NetscriptDefinitions.d.ts`, () => '', {
|
||||
virtual: true,
|
||||
});
|
||||
|
||||
describe("determineAllPossibilitiesForTabCompletion", function () {
|
||||
let closeServer: Server;
|
||||
let farServer: Server;
|
||||
@ -41,14 +49,14 @@ describe("determineAllPossibilitiesForTabCompletion", function () {
|
||||
AddToAllServers(farServer);
|
||||
});
|
||||
|
||||
it("completes the connect command", () => {
|
||||
const options = determineAllPossibilitiesForTabCompletion(Player, "connect ", 0);
|
||||
expect(options).equal(["8.8.8.8", "near"]);
|
||||
it("completes the connect command", async () => {
|
||||
const options = await determineAllPossibilitiesForTabCompletion(Player, "connect ", 0);
|
||||
expect(options).toEqual(["near"]);
|
||||
});
|
||||
|
||||
it("completes the buy command", () => {
|
||||
const options = determineAllPossibilitiesForTabCompletion(Player, "buy ", 0);
|
||||
expect(options).equal([
|
||||
it("completes the buy command", async () => {
|
||||
const options = await determineAllPossibilitiesForTabCompletion(Player, "buy ", 0);
|
||||
expect(options.sort()).toEqual([
|
||||
"BruteSSH.exe",
|
||||
"FTPCrack.exe",
|
||||
"relaySMTP.exe",
|
||||
@ -58,45 +66,48 @@ describe("determineAllPossibilitiesForTabCompletion", function () {
|
||||
"DeepscanV2.exe",
|
||||
"AutoLink.exe",
|
||||
"ServerProfiler.exe",
|
||||
]);
|
||||
"Formulas.exe",
|
||||
].sort());
|
||||
});
|
||||
|
||||
it("completes the scp command", () => {
|
||||
it("completes the scp command", async () => {
|
||||
Player.getHomeComputer().writeToTextFile("note.txt", "oh hai mark");
|
||||
Player.getHomeComputer().messages.push("af.lit");
|
||||
Player.getHomeComputer().writeToScriptFile("/www/script.js", "oh hai mark");
|
||||
const options1 = determineAllPossibilitiesForTabCompletion(Player, "scp ", 0);
|
||||
expect(options1).equal(["/www/script.js", "af.lit", "note.txt", "www/"]);
|
||||
const options1 = await determineAllPossibilitiesForTabCompletion(Player, "scp ", 0);
|
||||
expect(options1).toEqual(["/www/script.js", "af.lit", "note.txt", "www/"]);
|
||||
|
||||
const options2 = determineAllPossibilitiesForTabCompletion(Player, "scp note.txt ", 1);
|
||||
expect(options2).equal([Player.getHomeComputer().ip, "home", "8.8.8.8", "near", "4.4.4.4", "far"]);
|
||||
const options2 = await determineAllPossibilitiesForTabCompletion(Player, "scp note.txt ", 1);
|
||||
expect(options2).toEqual(["home", "near", "far"]);
|
||||
});
|
||||
|
||||
it("completes the kill, tail, mem, and check commands", () => {
|
||||
it("completes the kill, tail, mem, and check commands", async () => {
|
||||
Player.getHomeComputer().writeToScriptFile("/www/script.js", "oh hai mark");
|
||||
for (const command of ["kill", "tail", "mem", "check"]) {
|
||||
expect(determineAllPossibilitiesForTabCompletion(Player, `${command} `, 0)).equal(["/www/script.js", "www/"]);
|
||||
const options = await determineAllPossibilitiesForTabCompletion(Player, `${command} `, 0);
|
||||
expect(options).toEqual(["/www/script.js", "www/"]);
|
||||
}
|
||||
});
|
||||
|
||||
it("completes the nano commands", () => {
|
||||
it("completes the nano commands", async () => {
|
||||
Player.getHomeComputer().writeToScriptFile("/www/script.js", "oh hai mark");
|
||||
Player.getHomeComputer().writeToTextFile("note.txt", "oh hai mark");
|
||||
expect(determineAllPossibilitiesForTabCompletion(Player, "nano ", 0)).equal([
|
||||
const options = await determineAllPossibilitiesForTabCompletion(Player, "nano ", 0);
|
||||
expect(options).toEqual([
|
||||
"/www/script.js",
|
||||
"note.txt",
|
||||
".fconf",
|
||||
"www/",
|
||||
]);
|
||||
});
|
||||
|
||||
it("completes the rm command", () => {
|
||||
it("completes the rm command", async () => {
|
||||
Player.getHomeComputer().writeToTextFile("note.txt", "oh hai mark");
|
||||
Player.getHomeComputer().writeToScriptFile("/www/script.js", "oh hai mark");
|
||||
Player.getHomeComputer().contracts.push(new CodingContract("linklist.cct"));
|
||||
Player.getHomeComputer().messages.push("asl.msg");
|
||||
Player.getHomeComputer().messages.push("af.lit");
|
||||
expect(determineAllPossibilitiesForTabCompletion(Player, "rm ", 0)).equal([
|
||||
const options = await determineAllPossibilitiesForTabCompletion(Player, "rm ", 0);
|
||||
expect(options).toEqual([
|
||||
"/www/script.js",
|
||||
"NUKE.exe",
|
||||
"af.lit",
|
||||
@ -106,10 +117,11 @@ describe("determineAllPossibilitiesForTabCompletion", function () {
|
||||
]);
|
||||
});
|
||||
|
||||
it("completes the run command", () => {
|
||||
it("completes the run command", async () => {
|
||||
Player.getHomeComputer().writeToScriptFile("/www/script.js", "oh hai mark");
|
||||
Player.getHomeComputer().contracts.push(new CodingContract("linklist.cct"));
|
||||
expect(determineAllPossibilitiesForTabCompletion(Player, "run ", 0)).equal([
|
||||
const options = await determineAllPossibilitiesForTabCompletion(Player, "run ", 0);
|
||||
expect(options).toEqual([
|
||||
"/www/script.js",
|
||||
"NUKE.exe",
|
||||
"linklist.cct",
|
||||
@ -117,11 +129,12 @@ describe("determineAllPossibilitiesForTabCompletion", function () {
|
||||
]);
|
||||
});
|
||||
|
||||
it("completes the cat command", () => {
|
||||
it("completes the cat command", async () => {
|
||||
Player.getHomeComputer().writeToTextFile("/www/note.txt", "oh hai mark");
|
||||
Player.getHomeComputer().messages.push("asl.msg");
|
||||
Player.getHomeComputer().messages.push("af.lit");
|
||||
expect(determineAllPossibilitiesForTabCompletion(Player, "cat ", 0)).equal([
|
||||
const options = await determineAllPossibilitiesForTabCompletion(Player, "cat ", 0);
|
||||
expect(options).toEqual([
|
||||
"asl.msg",
|
||||
"af.lit",
|
||||
"/www/note.txt",
|
||||
@ -129,11 +142,13 @@ describe("determineAllPossibilitiesForTabCompletion", function () {
|
||||
]);
|
||||
});
|
||||
|
||||
it("completes the download and mv commands", () => {
|
||||
it("completes the download and mv commands", async () => {
|
||||
Player.getHomeComputer().writeToScriptFile("/www/script.js", "oh hai mark");
|
||||
Player.getHomeComputer().writeToTextFile("note.txt", "oh hai mark");
|
||||
for (const command of ["download", "mv"]) {
|
||||
expect(determineAllPossibilitiesForTabCompletion(Player, `${command} `, 0)).equal([
|
||||
|
||||
const options = await determineAllPossibilitiesForTabCompletion(Player, `${command} `, 0);
|
||||
expect(options).toEqual([
|
||||
"/www/script.js",
|
||||
"note.txt",
|
||||
"www/",
|
||||
@ -141,21 +156,24 @@ describe("determineAllPossibilitiesForTabCompletion", function () {
|
||||
}
|
||||
});
|
||||
|
||||
it("completes the cd command", () => {
|
||||
it("completes the cd command", async () => {
|
||||
Player.getHomeComputer().writeToScriptFile("/www/script.js", "oh hai mark");
|
||||
expect(determineAllPossibilitiesForTabCompletion(Player, "cd ", 0)).equal(["www/"]);
|
||||
const options = await determineAllPossibilitiesForTabCompletion(Player, "cd ", 0);
|
||||
expect(options).toEqual(["www/"]);
|
||||
});
|
||||
|
||||
it("completes the ls and cd commands", () => {
|
||||
it("completes the ls and cd commands", async () => {
|
||||
Player.getHomeComputer().writeToScriptFile("/www/script.js", "oh hai mark");
|
||||
for (const command of ["ls", "cd"]) {
|
||||
expect(determineAllPossibilitiesForTabCompletion(Player, `${command} `, 0)).equal(["www/"]);
|
||||
const options = await determineAllPossibilitiesForTabCompletion(Player, `${command} `, 0);
|
||||
expect(options).toEqual(["www/"]);
|
||||
}
|
||||
});
|
||||
|
||||
it("completes commands starting with ./", () => {
|
||||
it("completes commands starting with ./", async () => {
|
||||
Player.getHomeComputer().writeToScriptFile("/www/script.js", "oh hai mark");
|
||||
expect(determineAllPossibilitiesForTabCompletion(Player, "run ./", 0)).equal([
|
||||
const options = await determineAllPossibilitiesForTabCompletion(Player, "run ./", 0);
|
||||
expect(options).toEqual([
|
||||
".//www/script.js",
|
||||
"NUKE.exe",
|
||||
"./www/",
|
||||
|
Loading…
Reference in New Issue
Block a user