Merge remote-tracking branch 'upstream/dev' into dev

This commit is contained in:
rderfler 2022-04-25 13:32:38 -04:00
commit e381e857a5
93 changed files with 3791 additions and 1904 deletions

@ -9,6 +9,11 @@ SECTION is something like "API", "UI", "MISC", "STANEK", "CORPORATION"
FIX #xyzw is the issue number, if any
PLAYER DESCRIPTION is what you'd tell a non-contributor to convey what is changed.
# Linked issues
If your pull request is related to a git issue, please link it in the description using #xyz.
If your PR should close the issue when it is merged in, use `fixes #xyz` or `closes #xyz`. It'll automate the process.
# Documentation
- DO NOT CHANGE any markdown/\*.md, these files are autogenerated from NetscriptDefinitions.d.ts and will be overwritten

64
.github/workflows/validate-pr.yml vendored Normal file

@ -0,0 +1,64 @@
name: Validate PR
on:
pull_request:
branches: [dev]
types: [opened, edited, synchronize, reopened]
jobs:
checkTitle:
name: Check Title
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'
steps:
- name: Validate Title
id: validate-pr-title
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
echo "Creating label (if it does not exist)"
LABEL="validation: invalid title"
gh --repo "${{ github.repository }}" \
label create "$LABEL" --description "Modifications to this pull request are requested" --color D93F0B || true
PR_TITLE=$(\
gh --repo "${{ github.repository }}" \
pr view "${{ github.event.number }}" --json title --jq .title)
echo "::set-output name=title::$PR_TITLE"
echo "PR Title: $PR_TITLE"
TITLE_REGEX="^[0-9A-Z\-]*: .*$"
PR_TITLE_VALID=$(echo "$PR_TITLE" | grep -Eq "$TITLE_REGEX" && echo "true" || echo "false")
if [ "$PR_TITLE_VALID" == "true" ]; then
echo "Title is valid, removing label"
gh --repo "${{ github.repository }}" \
pr edit "${{ github.event.number }}" --remove-label "$LABEL" || true
else
echo "Invalid Title"
ERROR_MSG="$PR_TITLE -> should match -> $TITLE_REGEX"
echo "$ERROR_MSG"
echo "::set-output name=invalid::true"
echo "::set-output name=errorMessage::$ERROR_MSG"
touch comment.txt
echo "## The title \`$PR_TITLE\` should match \`$TITLE_REGEX\`" >> comment.txt
echo "" >> comment.txt
echo "SECTION: FIX #xzyw PLAYER DESCRIPTION" >> comment.txt
echo "" >> comment.txt
echo 'SECTION is something like "API", "UI", "MISC", "STANEK", "CORPORATION"' >> comment.txt
echo 'FIX #xyzw is the issue number, if any' >> comment.txt
echo "PLAYER DESCRIPTION is what you'd tell a non-contributor to convey what is changed." >> comment.txt
echo "Add pr label"
gh --repo "${{ github.repository }}" \
pr edit "${{ github.event.number }}" --add-label "$LABEL"
echo "And comment on the pr"
gh --repo "${{ github.repository }}" \
pr comment "${{ github.event.number }}" --body-file comment.txt
fi
- name: Flag workflow error
if: steps.validate-pr-title.outputs.invalid == 'true'
run: |
echo "${{ steps.validate-pr-title.outputs.errorMessage }}"
exit 1

4
dist/main.bundle.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

66
dist/vendor.bundle.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -84,24 +84,24 @@ The following is a list of all of the problem types that a Coding Contract can c
The list contains the name of (i.e. the value returned by
:js:func:`getContractType`) and a brief summary of the problem it poses.
+------------------------------------+------------------------------------------------------------------------------------------+
+-----------------------------------------+------------------------------------------------------------------------------------------+
| Name | Problem Summary |
+====================================+==========================================================================================+
+=========================================+==========================================================================================+
| Find Largest Prime Factor | | Given a number, find its largest prime factor. A prime factor |
| | | is a factor that is a prime number. |
+------------------------------------+------------------------------------------------------------------------------------------+
+-----------------------------------------+------------------------------------------------------------------------------------------+
| Subarray with Maximum Sum | | Given an array of integers, find the contiguous subarray (containing |
| | | at least one number) which has the largest sum and return that sum. |
+------------------------------------+------------------------------------------------------------------------------------------+
+-----------------------------------------+------------------------------------------------------------------------------------------+
| Total Ways to Sum | | Given a number, how many different distinct ways can that number be written as |
| | | a sum of at least two positive integers? |
+------------------------------------+------------------------------------------------------------------------------------------+
+-----------------------------------------+------------------------------------------------------------------------------------------+
| Total Ways to Sum II | | You are given an array with two elements. The first element is an integer n. |
| | | The second element is an array of numbers representing the set of available integers. |
| | | How many different distinct ways can that number n be written as |
| | | a sum of integers contained in the given set? |
| | | You may use each integer in the set zero or more times. |
+------------------------------------+------------------------------------------------------------------------------------------+
+-----------------------------------------+------------------------------------------------------------------------------------------+
| Spiralize Matrix | | Given an array of array of numbers representing a 2D matrix, return the |
| | | elements of that matrix in clockwise spiral order. |
| | | |
@ -112,7 +112,7 @@ The list contains the name of (i.e. the value returned by
| | | [9, 10, 11, 12] |
| | | |
| | | is [1, 2, 3, 4, 8, 12, 11, 10, 9, 5, 6, 7] |
+------------------------------------+------------------------------------------------------------------------------------------+
+-----------------------------------------+------------------------------------------------------------------------------------------+
| Array Jumping Game | | You are given an array of integers where each element represents the |
| | | maximum possible jump distance from that position. For example, if you |
| | | are at position i and your maximum jump length is n, then you can jump |
@ -120,7 +120,7 @@ The list contains the name of (i.e. the value returned by
| | | |
| | | Assuming you are initially positioned at the start of the array, determine |
| | | whether you are able to reach the last index of the array. |
+------------------------------------+------------------------------------------------------------------------------------------+
+-----------------------------------------+------------------------------------------------------------------------------------------+
| Array Jumping Game II | | You are given an array of integers where each element represents the |
| | | maximum possible jump distance from that position. For example, if you |
| | | are at position i and your maximum jump length is n, then you can jump |
@ -130,7 +130,7 @@ The list contains the name of (i.e. the value returned by
| | | the minimum number of jumps to reach the end of the array. |
| | | |
| | | If it's impossible to reach the end, then the answer should be 0. |
+------------------------------------+------------------------------------------------------------------------------------------+
+-----------------------------------------+------------------------------------------------------------------------------------------+
| Merge Overlapping Intervals | | Given an array of intervals, merge all overlapping intervals. An interval |
| | | is an array with two numbers, where the first number is always less than |
| | | the second (e.g. [1, 5]). |
@ -140,7 +140,7 @@ The list contains the name of (i.e. the value returned by
| | | Example: |
| | | [[1, 3], [8, 10], [2, 6], [10, 16]] |
| | | merges into [[1, 6], [8, 16]] |
+------------------------------------+------------------------------------------------------------------------------------------+
+-----------------------------------------+------------------------------------------------------------------------------------------+
| Generate IP Addresses | | Given a string containing only digits, return an array with all possible |
| | | valid IP address combinations that can be created from the string. |
| | | |
@ -150,7 +150,7 @@ The list contains the name of (i.e. the value returned by
| | | Examples: |
| | | 25525511135 -> [255.255.11.135, 255.255.111.35] |
| | | 1938718066 -> [193.87.180.66] |
+------------------------------------+------------------------------------------------------------------------------------------+
+-----------------------------------------+------------------------------------------------------------------------------------------+
| Algorithmic Stock Trader I | | You are given an array of numbers representing stock prices, where the |
| | | i-th element represents the stock price on day i. |
| | | |
@ -158,7 +158,7 @@ The list contains the name of (i.e. the value returned by
| | | transaction (i.e. you can buy an sell the stock once). If no profit |
| | | can be made, then the answer should be 0. Note that you must buy the stock |
| | | before you can sell it. |
+------------------------------------+------------------------------------------------------------------------------------------+
+-----------------------------------------+------------------------------------------------------------------------------------------+
| Algorithmic Stock Trader II | | You are given an array of numbers representing stock prices, where the |
| | | i-th element represents the stock price on day i. |
| | | |
@ -167,7 +167,7 @@ The list contains the name of (i.e. the value returned by
| | | share of the stock. Note that you cannot engage in multiple transactions at |
| | | once. In other words, you must sell the stock before you buy it again. If no |
| | | profit can be made, then the answer should be 0. |
+------------------------------------+------------------------------------------------------------------------------------------+
+-----------------------------------------+------------------------------------------------------------------------------------------+
| Algorithmic Stock Trader III | | You are given an array of numbers representing stock prices, where the |
| | | i-th element represents the stock price on day i. |
| | | |
@ -176,7 +176,7 @@ The list contains the name of (i.e. the value returned by
| | | of the stock. Note that you cannot engage in multiple transactions at once. |
| | | In other words, you must sell the stock before you buy it again. If no profit |
| | | can be made, then the answer should be 0. |
+------------------------------------+------------------------------------------------------------------------------------------+
+-----------------------------------------+------------------------------------------------------------------------------------------+
| Algorithmic Stock Trader IV | | You are given an array with two elements. The first element is an integer k. |
| | | The second element is an array of numbers representing stock prices, where the |
| | | i-th element represents the stock price on day i. |
@ -186,24 +186,24 @@ The list contains the name of (i.e. the value returned by
| | | Note that you cannot engage in multiple transactions at once. In other words, |
| | | you must sell the stock before you can buy it. If no profit can be made, then |
| | | the answer should be 0. |
+------------------------------------+------------------------------------------------------------------------------------------+
+-----------------------------------------+------------------------------------------------------------------------------------------+
| Minimum Path Sum in a Triangle | | You are given a 2D array of numbers (array of array of numbers) that represents a |
| | | triangle (the first array has one element, and each array has one more element than |
| | | the one before it, forming a triangle). Find the minimum path sum from the top to the |
| | | bottom of the triangle. In each step of the path, you may only move to adjacent |
| | | numbers in the row below. |
+------------------------------------+------------------------------------------------------------------------------------------+
+-----------------------------------------+------------------------------------------------------------------------------------------+
| Unique Paths in a Grid I | | You are given an array with two numbers: [m, n]. These numbers represent a |
| | | m x n grid. Assume you are initially positioned in the top-left corner of that |
| | | grid and that you are trying to reach the bottom-right corner. On each step, |
| | | you may only move down or to the right. |
| | | |
| | |
| | | |
| | | Determine how many unique paths there are from start to finish. |
+------------------------------------+------------------------------------------------------------------------------------------+
+-----------------------------------------+------------------------------------------------------------------------------------------+
| Unique Paths in a Grid II | | You are given a 2D array of numbers (array of array of numbers) representing |
| | | a grid. The 2D array contains 1's and 0's, where 1 represents an obstacle and |
| | |
| | | |
| | | 0 represents a free space. |
| | | |
| | | Assume you are initially positioned in top-left corner of that grid and that you |
@ -211,7 +211,7 @@ The list contains the name of (i.e. the value returned by
| | | or to the right. Furthermore, you cannot move onto spaces which have obstacles. |
| | | |
| | | Determine how many unique paths there are from start to finish. |
+------------------------------------+------------------------------------------------------------------------------------------+
+-----------------------------------------+------------------------------------------------------------------------------------------+
| Shortest Path in a Grid | | You are given a 2D array of numbers (array of array of numbers) representing |
| | | a grid. The 2D array contains 1's and 0's, where 1 represents an obstacle and |
| | | 0 represents a free space. |
@ -228,7 +228,7 @@ The list contains the name of (i.e. the value returned by
| | | [[0,1], |
| | | [1,0]] -> "" |
| | | |
+------------------------------------+------------------------------------------------------------------------------------------+
+-----------------------------------------+------------------------------------------------------------------------------------------+
| Sanitize Parentheses in Expression | | Given a string with parentheses and letters, remove the minimum number of invalid |
| | | parentheses in order to validate the string. If there are multiple minimal ways |
| | | to validate the string, provide all of the possible results. |
@ -240,7 +240,7 @@ The list contains the name of (i.e. the value returned by
| | | ()())() -> [()()(), (())()] |
| | | (a)())() -> [(a)()(), (a())()] |
| | | )( -> [""] |
+------------------------------------+------------------------------------------------------------------------------------------+
+-----------------------------------------+------------------------------------------------------------------------------------------+
| Find All Valid Math Expressions | | You are given a string which contains only digits between 0 and 9 as well as a target |
| | | number. Return all possible ways you can add the +, -, and * operators to the string |
| | | of digits such that it evaluates to the target number. |
@ -256,4 +256,117 @@ The list contains the name of (i.e. the value returned by
| | | |
| | | Input: digits = "105", target = 5 |
| | | Output: [1*0+5, 10-5] |
+------------------------------------+------------------------------------------------------------------------------------------+
+-----------------------------------------+------------------------------------------------------------------------------------------+
| HammingCodes: Integer to Encoded Binary | | You are given a decimal value. |
| | | Convert it into a binary string and encode it as a 'Hamming-Code'. eg: |
| | | Value 8 will result into binary '1000', which will be encoded |
| | | with the pattern 'pppdpddd', where p is a paritybit and d a databit, |
| | | or '10101' (Value 21) will result into (pppdpdddpd) '1001101011'. |
| | | NOTE: You need an parity Bit on Index 0 as an 'overall'-paritybit. |
| | | NOTE 2: You should watch the HammingCode-video from 3Blue1Brown, which |
| | | explains the 'rule' of encoding, |
| | | including the first Index parity-bit mentioned on the first note. |
| | | Now the only one rule for this encoding: |
| | | It's not allowed to add additional leading '0's to the binary value |
| | | That means, the binary value has to be encoded as it is |
+-----------------------------------------+------------------------------------------------------------------------------------------+
| HammingCodes: Encoded Binary to Integer | | You are given an encoded binary string. |
| | | Treat it as a Hammingcode with 1 'possible' error on an random Index. |
| | | Find the 'possible' wrong bit, fix it and extract the decimal value, which is |
| | | hidden inside the string.\n\n", |
| | | Note: The length of the binary string is dynamic, but it's encoding/decoding is |
| | | following Hammings 'rule'\n", |
| | | Note 2: Index 0 is an 'overall' parity bit. Watch the Hammingcode-video from |
| | | 3Blue1Brown for more information\n", |
| | | Note 3: There's a ~55% chance for an altered Bit. So... MAYBE |
| | | there is an altered Bit 😉\n", |
| | | Extranote for automation: return the decimal value as a string", |
+-----------------------------------------+------------------------------------------------------------------------------------------+
| Proper 2-Coloring of a Graph | | You are given data, representing a graph. Note that "graph", as used here, refers to |
| | | the field of graph theory, and has no relation to statistics or plotting. |
| | | |
| | | The first element of the data represents the number of vertices in the graph. Each |
| | | vertex is a unique number between 0 and ${data[0] - 1}. The next element of the data |
| | | represents the edges of the graph. |
| | | |
| | | Two vertices u,v in a graph are said to be adjacent if there exists an edge [u,v]. |
| | | Note that an edge [u,v] is the same as an edge [v,u], as order does not matter. |
| | | |
| | | You must construct a 2-coloring of the graph, meaning that you have to assign each |
| | | vertex in the graph a "color", either 0 or 1, such that no two adjacent vertices have |
| | | the same color. Submit your answer in the form of an array, where element i |
| | | represents the color of vertex i. If it is impossible to construct a 2-coloring of |
| | | the given graph, instead submit an empty array. |
| | | |
| | | Examples: |
| | | |
| | | Input: [4, [[0, 2], [0, 3], [1, 2], [1, 3]]] |
| | | Output: [0, 0, 1, 1] |
| | | |
| | | Input: [3, [[0, 1], [0, 2], [1, 2]]] |
| | | Output: [] |
+-----------------------------------------+------------------------------------------------------------------------------------------+
| Compression I: RLE Compression | | Run-length encoding (RLE) is a data compression technique which encodes data as a |
| | | series of runs of a repeated single character. Runs are encoded as a length, followed |
| | | by the character itself. Lengths are encoded as a single ASCII digit; runs of 10 |
| | | characters or more are encoded by splitting them into multiple runs. |
| | | |
| | | You are given a string as input. Encode it using run-length encoding with the minimum |
| | | possible output length. |
| | | |
| | | Examples: |
| | | aaaaabccc -> 5a1b3c |
| | | aAaAaA -> 1a1A1a1A1a1A |
| | | 111112333 -> 511233 |
| | | zzzzzzzzzzzzzzzzzzz -> 9z9z1z (or 9z8z2z, etc.) |
+-----------------------------------------+------------------------------------------------------------------------------------------+
| Compression II: LZ Decompression | | Lempel-Ziv (LZ) compression is a data compression technique which encodes data using |
| | | references to earlier parts of the data. In this variant of LZ, data is encoded in two |
| | | types of chunk. Each chunk begins with a length L, encoded as a single ASCII digit |
| | | from 1 - 9, followed by the chunk data, which is either: |
| | | |
| | | 1. Exactly L characters, which are to be copied directly into the uncompressed data. |
| | | 2. A reference to an earlier part of the uncompressed data. To do this, the length |
| | | is followed by a second ASCII digit X: each of the L output characters is a copy |
| | | of the character X places before it in the uncompressed data. |
| | | |
| | | For both chunk types, a length of 0 instead means the chunk ends immediately, and the |
| | | next character is the start of a new chunk. The two chunk types alternate, starting |
| | | with type 1, and the final chunk may be of either type. |
| | | |
| | | You are given an LZ-encoded string. Decode it and output the original string. |
| | | |
| | | Example: decoding '5aaabc340533bca' chunk-by-chunk |
| | | 5aaabc -> aaabc |
| | | 5aaabc34 -> aaabcaab |
| | | 5aaabc340 -> aaabcaab |
| | | 5aaabc34053 -> aaabcaabaabaa |
| | | 5aaabc340533bca -> aaabcaabaabaabca |
+-----------------------------------------+------------------------------------------------------------------------------------------+
| Compression III: LZ Compression | | Lempel-Ziv (LZ) compression is a data compression technique which encodes data using |
| | | references to earlier parts of the data. In this variant of LZ, data is encoded in two |
| | | types of chunk. Each chunk begins with a length L, encoded as a single ASCII digit |
| | | from 1 - 9, followed by the chunk data, which is either: |
| | | |
| | | 1. Exactly L characters, which are to be copied directly into the uncompressed data. |
| | | 2. A reference to an earlier part of the uncompressed data. To do this, the length |
| | | is followed by a second ASCII digit X: each of the L output characters is a copy |
| | | of the character X places before it in the uncompressed data. |
| | | |
| | | For both chunk types, a length of 0 instead means the chunk ends immediately, and the |
| | | next character is the start of a new chunk. The two chunk types alternate, starting |
| | | with type 1, and the final chunk may be of either type. |
| | | |
| | | You are given a string as input. Encode it using Lempel-Ziv encoding with the minimum |
| | | possible output length. |
| | | |
| | | Examples (some have other possible encodings of minimal length): |
| | | abracadabra -> 7abracad47 |
| | | mississippi -> 4miss433ppi |
| | | aAAaAAaAaAA -> 3aAA53035 |
| | | 2718281828 -> 627182844 |
| | | abcdefghijk -> 9abcdefghi02jk |
| | | aaaaaaaaaaa -> 1a911a |
| | | aaaaaaaaaaaa -> 1a912aa |
| | | aaaaaaaaaaaaa -> 1a91031 |
+-----------------------------------------+------------------------------------------------------------------------------------------+

@ -280,7 +280,7 @@ Description
In this BitNode:
* Your stats are significantly decreased
* You cannnot purchase additional servers
* You cannot purchase additional servers
* Hacking is significantly less profitable
Source-File

@ -1,5 +1,4 @@
module.exports = {
setupFiles: ["./jest.setup.js"],
moduleFileExtensions: ["ts", "tsx", "js", "jsx"],
transform: {
"^.+\\.(js|jsx|ts|tsx)$": "babel-jest",

@ -1,2 +0,0 @@
import "regenerator-runtime/runtime";
global.$ = require("jquery");

11
lore/bitnodes-general.txt Normal file

@ -0,0 +1,11 @@
BitNodes are advanced simulations used to contain humanity by the Enders. It is
unknown how or why they operate, but what is clear is that the World Daemon is
extremely important to the operation of a BitNode. It is possible for the daemon
to be hacked, which results in the entire simulation going offline and the
failure of automatic attempts to reboot the node. The Daemon has a physical
presence, as indicated by the Bladeburners' ability to destroy it via force.
Also, hydroflame (irl) has stated that the glitch in Ishima is the physical
location of the World Daemon in a node. When the player destroys a BitNode, it is
currently unknown what becomes of it, or the people trapped within. However, based
on the way jump3r and Deadalus help the player to destroy it, doing so is somehow
aligned with their goals.

5
lore/enders.txt Normal file

@ -0,0 +1,5 @@
The "Enders", as dubbed by the humans who know of them, are a humanoid alien race
with extremely advanced technology. "Many decades ago", they invaded Earth, leading
to war between the humans and enders, but the enders were far too powerful for the
humans to win against. When the enders had won, they, for reasons unknown, kept some
number of humans alive, and in some way contained the humans within BitNodes.

24
package-lock.json generated

@ -5046,9 +5046,9 @@
}
},
"node_modules/async": {
"version": "2.6.3",
"resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz",
"integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==",
"version": "2.6.4",
"resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz",
"integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==",
"dev": true,
"dependencies": {
"lodash": "^4.17.14"
@ -10199,9 +10199,9 @@
}
},
"node_modules/getos/node_modules/async": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/async/-/async-3.2.1.tgz",
"integrity": "sha512-XdD5lRO/87udXCMC9meWdYiR+Nq6ZjUfXidViUZGu2F1MO4T3XwZ1et0hb2++BgLfhyJwy44BGB/yx80ABx8hg==",
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/async/-/async-3.2.3.tgz",
"integrity": "sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g==",
"dev": true
},
"node_modules/getpass": {
@ -26245,9 +26245,9 @@
"dev": true
},
"async": {
"version": "2.6.3",
"resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz",
"integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==",
"version": "2.6.4",
"resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz",
"integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==",
"dev": true,
"requires": {
"lodash": "^4.17.14"
@ -30405,9 +30405,9 @@
},
"dependencies": {
"async": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/async/-/async-3.2.1.tgz",
"integrity": "sha512-XdD5lRO/87udXCMC9meWdYiR+Nq6ZjUfXidViUZGu2F1MO4T3XwZ1et0hb2++BgLfhyJwy44BGB/yx80ABx8hg==",
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/async/-/async-3.2.3.tgz",
"integrity": "sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g==",
"dev": true
}
}

@ -17,8 +17,7 @@
"@mui/icons-material": "^5.0.3",
"@mui/material": "^5.0.3",
"@mui/styles": "^5.0.1",
"@types/bcrypt": "^5.0.0",
"@types/bcryptjs": "^2.4.2",
"@mui/system": "^5.0.3",
"acorn": "^8.4.1",
"acorn-walk": "^8.1.1",
"arg": "^5.0.0",
@ -26,10 +25,8 @@
"better-react-mathjax": "^1.0.3",
"clsx": "^1.1.1",
"date-fns": "^2.25.0",
"electron-config": "^2.0.0",
"escodegen": "^1.11.0",
"file-saver": "^1.3.8",
"fs": "^0.0.1-security",
"jquery": "^3.5.0",
"js-sha256": "^0.9.0",
"jszip": "^3.7.0",
@ -57,22 +54,24 @@
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.1",
"@testing-library/cypress": "^8.0.1",
"@types/acorn": "^4.0.6",
"@types/bcryptjs": "^2.4.2",
"@types/escodegen": "^0.0.7",
"@types/file-saver": "^2.0.3",
"@types/jquery": "^3.5.14",
"@types/lodash": "^4.14.168",
"@types/numeral": "0.0.25",
"@types/numeral": "^2.0.2",
"@types/react": "^17.0.21",
"@types/react-beautiful-dnd": "^13.1.2",
"@types/react-dom": "^17.0.9",
"@types/react-resizable": "^1.7.3",
"@typescript-eslint/eslint-plugin": "^4.22.0",
"@typescript-eslint/parser": "^4.22.0",
"@typescript-eslint/eslint-plugin": "^5.20.0",
"@typescript-eslint/parser": "^5.20.0",
"babel-jest": "^27.0.6",
"babel-loader": "^8.0.5",
"cypress": "^8.3.1",
"electron": "^14.2.4",
"electron-packager": "^15.4.0",
"eslint": "^7.24.0",
"eslint": "^8.13.0",
"file-loader": "^6.2.0",
"fork-ts-checker-webpack-plugin": "^6.3.3",
"html-webpack-plugin": "^3.2.0",
@ -84,17 +83,15 @@
"prettier": "^2.3.2",
"raw-loader": "^4.0.2",
"react-refresh": "^0.10.0",
"regenerator-runtime": "^0.13.9",
"source-map": "^0.7.3",
"start-server-and-test": "^1.14.0",
"typescript": "^4.2.4",
"webpack": "^4.46.0",
"webpack-cli": "^3.3.12",
"webpack-dev-middleware": "^3.7.3",
"webpack-dev-server": "^3.11.2"
},
"engines": {
"node": ">=8 || <=9"
"node": ">=14"
},
"homepage": "https://github.com/danielyxie/bitburner",
"repository": {

@ -113,7 +113,7 @@ export const achievements: IMap<Achievement> = {
},
THE_COVENANT: {
...achievementData["THE_COVENANT"],
Icon: FactionNames.TheCovenant.toLowerCase(),
Icon: FactionNames.TheCovenant.toLowerCase().replace(/ /g, ""),
Condition: () => Player.factions.includes(FactionNames.TheCovenant),
},
[FactionNames.Illuminati.toUpperCase()]: {
@ -776,6 +776,7 @@ export const achievements: IMap<Achievement> = {
// { ID: FactionNames.Bladeburners.toUpperCase(), Condition: () => Player.factions.includes(FactionNames.Bladeburners) },
// { ID: "DEEPSCANV1.EXE", Condition: () => Player.getHomeComputer().programs.includes(Programs.DeepscanV1.name) },
// { ID: "DEEPSCANV2.EXE", Condition: () => Player.getHomeComputer().programs.includes(Programs.DeepscanV2.name) },
// { ID: "INFILTRATORS", Condition: () => Player.factions.includes(FactionNames.Infiltrators) },
// {
// ID: "SERVERPROFILER.EXE",
// Condition: () => Player.getHomeComputer().programs.includes(Programs.ServerProfiler.name),

@ -8,6 +8,7 @@ import { numeralWrapper } from "../ui/numeralFormat";
import { Money } from "../ui/React/Money";
import { Generic_fromJSON, Generic_toJSON, Reviver } from "../utils/JSONReviver";
import { FactionNames } from "../Faction/data/FactionNames";
export interface IConstructorParams {
info: string | JSX.Element;
@ -49,6 +50,12 @@ export interface IConstructorParams {
bladeburner_stamina_gain_mult?: number;
bladeburner_analysis_mult?: number;
bladeburner_success_chance_mult?: number;
infiltration_base_rep_increase?: number;
infiltration_rep_mult?: number;
infiltration_trade_mult?: number;
infiltration_sell_mult?: number;
infiltration_timer_mult?: number;
infiltration_damage_reduction_mult?: number;
startingMoney?: number;
programs?: string[];
@ -337,6 +344,50 @@ function generateStatsDescription(mults: IMap<number>, programs?: string[], star
<br />+{f(mults.bladeburner_success_chance_mult - 1)} Bladeburner Contracts and Operations success chance
</>
);
if (mults.infiltration_base_rep_increase)
desc = (
<>
{desc}
<br />+{f(mults.infiltration_base_rep_increase - 1)} Infiltration {FactionNames.ShadowsOfAnarchy} Reputation
base reward
</>
);
if (mults.infiltration_rep_mult)
desc = (
<>
{desc}
<br />+{f(mults.infiltration_rep_mult - 1)} Infiltration {FactionNames.ShadowsOfAnarchy} Reputation reward
</>
);
if (mults.infiltration_trade_mult)
desc = (
<>
{desc}
<br />+{f(mults.infiltration_trade_mult - 1)} Infiltration Reputation for trading information
</>
);
if (mults.infiltration_sell_mult)
desc = (
<>
{desc}
<br />+{f(mults.infiltration_sell_mult - 1)} Infiltration cash reward for selling information
</>
);
if (mults.infiltration_timer_mult)
desc = (
<>
{desc}
<br />+{f(mults.infiltration_timer_mult - 1)} Infiltration time per minigame
</>
);
if (mults.infiltration_damage_reduction_mult)
desc = (
<>
{desc}
<br />
{f(mults.infiltration_damage_reduction_mult - 1)} Infiltration health lost per failed minigame
</>
);
if (startingMoney)
desc = (
@ -390,6 +441,9 @@ export class Augmentation {
// Initial cost. Doesn't change when you purchase multiple Augmentation
startingCost = 0;
// Initial rep requirement. Doesn't change when you purchase multiple Augmentation
startingRepRequirement = 0;
// Factions that offer this aug.
factions: string[] = [];
@ -409,6 +463,7 @@ export class Augmentation {
this.baseRepRequirement = params.repCost;
this.baseCost = params.moneyCost;
this.startingCost = this.baseCost;
this.startingRepRequirement = this.baseRepRequirement;
this.factions = params.factions;
if (params.isSpecial) {
@ -509,6 +564,25 @@ export class Augmentation {
this.mults.bladeburner_success_chance_mult = params.bladeburner_success_chance_mult;
}
if (params.infiltration_base_rep_increase) {
this.mults.infiltration_base_rep_increase = params.infiltration_base_rep_increase;
}
if (params.infiltration_rep_mult) {
this.mults.infiltration_rep_mult = params.infiltration_rep_mult;
}
if (params.infiltration_trade_mult) {
this.mults.infiltration_trade_mult = params.infiltration_trade_mult;
}
if (params.infiltration_sell_mult) {
this.mults.infiltration_sell_mult = params.infiltration_sell_mult;
}
if (params.infiltration_timer_mult) {
this.mults.infiltration_timer_mult = params.infiltration_timer_mult;
}
if (params.infiltration_damage_reduction_mult) {
this.mults.infiltration_damage_reduction_mult = params.infiltration_damage_reduction_mult;
}
if (params.stats === undefined)
this.stats = generateStatsDescription(this.mults, params.programs, params.startingMoney);
else this.stats = params.stats;

@ -3,7 +3,6 @@ import { Augmentations } from "./Augmentations";
import { PlayerOwnedAugmentation, IPlayerOwnedAugmentation } from "./PlayerOwnedAugmentation";
import { AugmentationNames } from "./data/AugmentationNames";
import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers";
import { CONSTANTS } from "../Constants";
import { Factions, factionExists } from "../Faction/Factions";
import { Player } from "../Player";
@ -17,9 +16,11 @@ import {
initBladeburnerAugmentations,
initChurchOfTheMachineGodAugmentations,
initGeneralAugmentations,
initSoAAugmentations,
initNeuroFluxGovernor,
initUnstableCircadianModulator,
} from "./AugmentationCreator";
} from "./data/AugmentationCreator";
import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers";
import { Router } from "../ui/GameRoot";
export function AddToAugmentations(aug: Augmentation): void {
@ -50,6 +51,7 @@ function createAugmentations(): void {
initNeuroFluxGovernor(),
initUnstableCircadianModulator(),
...initGeneralAugmentations(),
...initSoAAugmentations(),
...(factionExists(FactionNames.Bladeburners) ? initBladeburnerAugmentations() : []),
...(factionExists(FactionNames.ChurchOfTheMachineGod) ? initChurchOfTheMachineGodAugmentations() : []),
].map(resetAugmentation);
@ -82,20 +84,36 @@ function updateNeuroFluxGovernorCosts(neuroFluxGovernorAugmentation: Augmentatio
let nextLevel = getNextNeuroFluxLevel();
--nextLevel;
const multiplier = Math.pow(CONSTANTS.NeuroFluxGovernorLevelMult, nextLevel);
neuroFluxGovernorAugmentation.baseRepRequirement *= multiplier * BitNodeMultipliers.AugmentationRepCost;
neuroFluxGovernorAugmentation.baseCost *= multiplier * BitNodeMultipliers.AugmentationMoneyCost;
neuroFluxGovernorAugmentation.baseRepRequirement =
neuroFluxGovernorAugmentation.startingRepRequirement * multiplier * BitNodeMultipliers.AugmentationRepCost;
neuroFluxGovernorAugmentation.baseCost =
neuroFluxGovernorAugmentation.startingCost * multiplier * BitNodeMultipliers.AugmentationMoneyCost;
for (let i = 0; i < Player.queuedAugmentations.length - 1; ++i) {
for (let i = 0; i < Player.queuedAugmentations.length; ++i) {
neuroFluxGovernorAugmentation.baseCost *= getBaseAugmentationPriceMultiplier();
}
}
function updateSoACosts(soaAugmentation: Augmentation): void {
const soaAugmentationNames = initSoAAugmentations().map((augmentation) => augmentation.name);
const soaAugCount = soaAugmentationNames.filter((augmentationName) =>
Player.hasAugmentation(augmentationName),
).length;
soaAugmentation.baseCost = soaAugmentation.startingCost * Math.pow(CONSTANTS.SoACostMult, soaAugCount);
if (soaAugmentationNames.find((augmentationName) => augmentationName === soaAugmentation.name)) {
soaAugmentation.baseRepRequirement =
soaAugmentation.startingRepRequirement * Math.pow(CONSTANTS.SoARepMult, soaAugCount);
}
}
export function updateAugmentationCosts(): void {
for (const name of Object.keys(Augmentations)) {
if (Augmentations.hasOwnProperty(name)) {
const augmentationToUpdate = Augmentations[name];
if (augmentationToUpdate.name === AugmentationNames.NeuroFluxGovernor) {
updateNeuroFluxGovernorCosts(augmentationToUpdate);
} else if (augmentationToUpdate.factions.includes(FactionNames.ShadowsOfAnarchy)) {
updateSoACosts(augmentationToUpdate);
} else {
augmentationToUpdate.baseCost =
augmentationToUpdate.startingCost *

@ -1,11 +1,11 @@
import { Augmentation, IConstructorParams } from "./Augmentation";
import { AugmentationNames } from "./data/AugmentationNames";
import { Player } from "../Player";
import { Programs } from "../Programs/Programs";
import { WHRNG } from "../Casino/RNG";
import { Augmentation, IConstructorParams } from "../Augmentation";
import { AugmentationNames } from "./AugmentationNames";
import { Player } from "../../Player";
import { Programs } from "../../Programs/Programs";
import { WHRNG } from "../../Casino/RNG";
import React from "react";
import { FactionNames } from "../Faction/data/FactionNames";
import { CONSTANTS } from "../Constants";
import { FactionNames } from "../../Faction/data/FactionNames";
import { CONSTANTS } from "../../Constants";
function getRandomBonus(): any {
const bonuses = [
@ -95,6 +95,99 @@ function getRandomBonus(): any {
return bonuses[Math.floor(bonuses.length * randomNumber.random())];
}
export const initSoAAugmentations = (): Augmentation[] => [
new Augmentation({
name: AugmentationNames.WKSharmonizer,
repCost: 1e4,
moneyCost: 1e6,
info:
`A copy of the WKS harmonizer from the MIA leader of the ${FactionNames.ShadowsOfAnarchy} ` +
"injects *Γ-based cells that provides general enhancement to the body.",
stats: (
<>
This augmentation makes many aspect of infiltration easier and more productive. Such as increased timer,
rewards, reduced damage taken, etc.
</>
),
factions: [FactionNames.ShadowsOfAnarchy],
}),
new Augmentation({
name: AugmentationNames.MightOfAres,
repCost: 1e4,
moneyCost: 1e6,
info:
"Extra-occular neurons taken from old martial art master. Injecting the user the ability to " +
"predict enemy attack before they even know it themself.",
stats: (
<>This augmentation makes the Slash minigame easier by showing you via an indictor when the slash in coming.</>
),
factions: [FactionNames.ShadowsOfAnarchy],
}),
new Augmentation({
name: AugmentationNames.WisdomOfAthena,
repCost: 1e4,
moneyCost: 1e6,
info: "A connective brain implant to SASHA that focuses in pattern recognition and predictive templating.",
stats: <>This augmentation makes the Bracket minigame easier by removing all '[' ']'.</>,
factions: [FactionNames.ShadowsOfAnarchy],
}),
new Augmentation({
name: AugmentationNames.ChaosOfDionysus,
repCost: 1e4,
moneyCost: 1e6,
info: "Opto-occipito implant to process visual signal before brain interpretation.",
stats: <>This augmentation makes the Backwards minigame easier by flipping the words.</>,
factions: [FactionNames.ShadowsOfAnarchy],
}),
new Augmentation({
name: AugmentationNames.BeautyOfAphrodite,
repCost: 1e4,
moneyCost: 1e6,
info:
"Pheromone extruder injected in the thoracodorsal nerve. Emits pleasing scent guaranteed to " +
"make conversational partners more agreeable.",
stats: <>This augmentation makes the Bribe minigame easier by indicating the incorrect paths.</>,
factions: [FactionNames.ShadowsOfAnarchy],
}),
new Augmentation({
name: AugmentationNames.TrickeryOfHermes,
repCost: 1e4,
moneyCost: 1e6,
info: "Penta-dynamo-neurovascular-valve inserted in the carpal ligament, enhances dexterity.",
stats: <>This augmentation makes the Cheat Code minigame easier by allowing the opposite character.</>,
factions: [FactionNames.ShadowsOfAnarchy],
}),
new Augmentation({
name: AugmentationNames.FloodOfPoseidon,
repCost: 1e4,
moneyCost: 1e6,
info: "Transtinatium VVD reticulator used in optico-sterbing recognition.",
stats: <>This augmentation makes the Symbol matching minigame easier by indicating the correct choice.</>,
factions: [FactionNames.ShadowsOfAnarchy],
}),
new Augmentation({
name: AugmentationNames.HuntOfArtemis,
repCost: 1e4,
moneyCost: 1e6,
info: "magneto-turboencabulator based on technology by Micha Eike Siemon, increases the users electro-magnetic sensitivity.",
stats: (
<>
This augmentation makes the Minesweeper minigame easier by showing the location of all mines and keeping their
position.
</>
),
factions: [FactionNames.ShadowsOfAnarchy],
}),
new Augmentation({
name: AugmentationNames.KnowledgeOfApollo,
repCost: 1e4,
moneyCost: 1e6,
info: "Neodynic retention fjengeln spoofer using -φ karmions, net positive effect on implantees delta wave.",
stats: <>This augmentation makes the Wire Cutting minigame easier by indicating the incorrect wires.</>,
factions: [FactionNames.ShadowsOfAnarchy],
}),
];
export const initGeneralAugmentations = (): Augmentation[] => [
new Augmentation({
name: AugmentationNames.HemoRecirculator,
@ -1894,7 +1987,6 @@ export const initChurchOfTheMachineGodAugmentations = (): Augmentation[] => [
export function initNeuroFluxGovernor(): Augmentation {
const donationBonus = CONSTANTS.Donations / 1e6 / 100; // 1 millionth of a percent per donation
console.log(donationBonus * 100);
return new Augmentation({
name: AugmentationNames.NeuroFluxGovernor,
repCost: 500,
@ -1907,7 +1999,8 @@ export function initNeuroFluxGovernor(): Augmentation {
stats: (
<>
This special augmentation can be leveled up infinitely. Each level of this augmentation increases MOST
multipliers by 1% (+{donationBonus * 100}% boosted by real life blood donations), stacking multiplicatively.
multipliers by 1% (+{(donationBonus * 100).toFixed(6)}% boosted by real life blood donations), stacking
multiplicatively.
</>
),
hacking_chance_mult: 1.01 + donationBonus,
@ -1936,7 +2029,12 @@ export function initNeuroFluxGovernor(): Augmentation {
hacknet_node_core_cost_mult: 1 / (1.01 + donationBonus),
hacknet_node_level_cost_mult: 1 / (1.01 + donationBonus),
work_money_mult: 1.01 + donationBonus,
factions: Object.values(FactionNames),
factions: Object.values(FactionNames).filter(
(factionName) =>
![FactionNames.ShadowsOfAnarchy, FactionNames.Bladeburners, FactionNames.ChurchOfTheMachineGod].includes(
factionName,
),
),
});
}

@ -114,6 +114,28 @@ export enum AugmentationNames {
StaneksGift2 = "Stanek's Gift - Awakening",
StaneksGift3 = "Stanek's Gift - Serenity",
/*
MightOfAres = "Might of Ares", // slash
WisdomOfAthena = "Wisdom of Athena", // bracket
TrickeryOfHermes = "Trickery of Hermes", // cheatcode
BeautyOfAphrodite = "Beauty of Aphrodite", // bribe
ChaosOfDionysus = "Chaos of Dionysus", // reverse
FloodOfPoseidon = "Flood of Poseidon", // hex
HuntOfArtemis = "Hunt of Artemis", // mine
KnowledgeOfApollo = "Knowledge of Apollo", // wire
*/
// Infiltrators MiniGames
MightOfAres = "SoA - Might of Ares", // slash
WisdomOfAthena = "SoA - Wisdom of Athena", // bracket
TrickeryOfHermes = "SoA - Trickery of Hermes", // cheatcode
BeautyOfAphrodite = "SoA - Beauty of Aphrodite", // bribe
ChaosOfDionysus = "SoA - Chaos of Dionysus", // reverse
FloodOfPoseidon = "SoA - Flood of Poseidon", // hex
HuntOfArtemis = "SoA - Hunt of Artemis", // mine
KnowledgeOfApollo = "SoA - Knowledge of Apollo", // wire
WKSharmonizer = "SoA - phyzical WKS harmonizer",
//Wasteland Augs
//PepBoy: "P.E.P-Boy", Plasma Energy Projection System
//PepBoyForceField Generates plasma force fields

@ -172,7 +172,7 @@ export function AugmentationsRoot(props: IProps): React.ReactElement {
</span>
</Tooltip>
<Tooltip title={<Typography>It's always a good idea to backup/export your save!</Typography>}>
<Button sx={{ width: "100%" }} onClick={doExport} color="error">
<Button sx={{ width: "100%", color: Settings.theme.successlight }} onClick={doExport}>
Backup Save {exportBonusStr()}
</Button>
</Tooltip>

@ -1,5 +1,5 @@
import React from "react";
import { BitNodeMultipliers } from "./BitNodeMultipliers";
import { BitNodeMultipliers, IBitNodeMultipliers } from "./BitNodeMultipliers";
import { IPlayer } from "../PersonObjects/IPlayer";
import { IMap } from "../types";
import { FactionNames } from "../Faction/data/FactionNames";
@ -73,15 +73,6 @@ BitNodes["BitNode2"] = new BitNode(
savagery. The organized crime factions quickly rose to the top of the modern world.
<br />
<br />
In this BitNode:
<br />
<br />
Your hacking level is reduced by 20%
<br />
The growth rate and maximum amount of money available on servers are significantly decreased
<br />
The amount of money gained from crimes and Infiltration is tripled
<br />
Certain Factions ({FactionNames.SlumSnakes}, {FactionNames.Tetrads}, {FactionNames.TheSyndicate},{" "}
{FactionNames.TheDarkArmy}, {FactionNames.SpeakersForTheDead}, {FactionNames.NiteSec}, {FactionNames.TheBlackHand}
) give the player the ability to form and manage their own gangs. These gangs will earn the player money and
@ -89,10 +80,6 @@ BitNodes["BitNode2"] = new BitNode(
<br />
Every Augmentation in the game will be available through the Factions listed above
<br />
For every Faction NOT listed above, reputation gains are halved
<br />
You will no longer gain passive reputation with Factions
<br />
<br />
Destroying this BitNode will give you Source-File 2, or if you already have this Source-File it will upgrade its
level up to a maximum of 3. This Source-File allows you to form gangs in other BitNodes once your karma decreases
@ -123,15 +110,7 @@ BitNodes["BitNode3"] = new BitNode(
<br />
<br />
In this BitNode you can create and manage your own corporation. Running a successful corporation has the potential
of generating massive profits. All other forms of income are reduced by 75%. Furthermore: <br />
<br />
The price and reputation cost of all Augmentations is tripled
<br />
The starting and maximum amount of money on servers is reduced by 75%
<br />
Server growth rate is reduced by 80%
<br />
You now only need 75 favour with a faction in order to donate to it, rather than 150
of generating massive profits.
<br />
<br />
Destroying this BitNode will give you Source-File 3, or if you already have this Source-File it will upgrade its
@ -157,9 +136,6 @@ BitNodes["BitNode4"] = new BitNode(
The Singularity has arrived. The human race is gone, replaced by artificially superintelligent beings that are
more machine than man. <br />
<br />
In this BitNode, progressing is significantly harder. Experience gain rates for all stats are reduced. Most
methods of earning money will now give significantly less.
<br />
<br />
In this BitNode you will gain access to a new set of Netscript Functions known as Singularity Functions. These
functions allow you to control most aspects of the game through scripts, including working for factions/companies,
@ -184,24 +160,6 @@ BitNodes["BitNode5"] = new BitNode(
couldn't be modeled by 1's and 0's. They were wrong.
<br />
<br />
In this BitNode:
<br />
<br />
The base security level of servers is doubled
<br />
The starting money on servers is halved, but the maximum money remains the same
<br />
Most methods of earning money now give significantly less
<br />
Infiltration gives 50% more reputation and money
<br />
Corporations have 50% lower valuations and are therefore less profitable
<br />
Augmentations are more expensive
<br />
Hacking experience gain rates are reduced
<br />
<br />
Destroying this BitNode will give you Source-File 5, or if you already have this Source-File it will upgrade its
level up to a maximum of 3. This Source-File grants you a special new stat called Intelligence. Intelligence is
unique because it is permanent and persistent (it never gets reset back to 1). However gaining Intelligence
@ -235,20 +193,7 @@ BitNodes["BitNode6"] = new BitNode(
<br />
<br />
In this BitNode you will be able to access the {FactionNames.Bladeburners} Division at the NSA, which provides a
new mechanic for progression. Furthermore:
<br />
<br />
Hacking and Hacknet Nodes will be less profitable
<br />
Your hacking level is reduced by 65%
<br />
Hacking experience gain from scripts is reduced by 75%
<br />
Corporations have 80% lower valuations and are therefore less profitable
<br />
Working for companies is 50% less profitable
<br />
Crimes and Infiltration are 25% less profitable
new mechanic for progression.
<br />
<br />
Destroying this BitNode will give you Source-File 6, or if you already have this Source-File it will upgrade its
@ -281,25 +226,7 @@ BitNodes["BitNode7"] = new BitNode(
<br />
In this BitNode you will be able to access the {FactionNames.Bladeburners} API, which allows you to access{" "}
{FactionNames.Bladeburners}
functionality through Netscript. Furthermore: <br />
<br />
The rank you gain from {FactionNames.Bladeburners} contracts/operations is reduced by 40%
<br />
{FactionNames.Bladeburners} skills cost twice as many skill points
<br />
Augmentations are 3x more expensive
<br />
Hacking and Hacknet Nodes will be significantly less profitable
<br />
Your hacking level is reduced by 65%
<br />
Hacking experience gain from scripts is reduced by 75%
<br />
Corporations have 80% lower valuations and are therefore less profitable
<br />
Working for companies is 50% less profitable
<br />
Crimes and Infiltration are 25% less profitable
functionality through Netscript.
<br />
<br />
Destroying this BitNode will give you Source-File 7, or if you already have this Source-File it will upgrade its
@ -331,14 +258,10 @@ BitNodes["BitNode8"] = new BitNode(
<br />
You start with $250 million
<br />
The only way to earn money is by trading on the stock market
<br />
You start with a WSE membership and access to the TIX API
<br />
You are able to short stocks and place different types of orders (limit/stop)
<br />
You can immediately donate to factions to gain reputation
<br />
<br />
Destroying this BitNode will give you Source-File 8, or if you already have this Source-File it will upgrade its
level up to a maximum of 3. This Source-File grants the following benefits:
@ -378,16 +301,6 @@ BitNodes["BitNode9"] = new BitNode(
which can be spent on a variety of different upgrades.
<br />
<br />
In this BitNode:
<br />
<br />
Your stats are significantly decreased
<br />
You cannnot purchase additional servers
<br />
Hacking is significantly less profitable
<br />
<br />
Destroying this BitNode will give you Source-File 9, or if you already have this Source-File it will upgrade its
level up to a maximum of 3. This Source-File grants the following benefits:
<br />
@ -432,19 +345,7 @@ BitNodes["BitNode10"] = new BitNode(
1. Grafting: Visit VitaLife in New Tokyo to be able to obtain Augmentations without needing to install
<br />
2. Duplicate Sleeves: Duplicate your consciousness into Synthoids, allowing you to perform different tasks
synchronously
<br />
<br />
In this BitNode:
<br />
<br />
Your stats are significantly decreased
<br />
All methods of gaining money are half as profitable (except Stock Market)
<br />
Purchased servers are more expensive, have less max RAM, and a lower maximum limit
<br />
Augmentations are 5x as expensive and require twice as much reputation
synchronously.
<br />
<br />
Destroying this BitNode will give you Source-File 10, or if you already have this Source-File it will upgrade its
@ -472,28 +373,6 @@ BitNodes["BitNode11"] = new BitNode(
world is slowly crumbling in the middle of the biggest economic crisis of all time.
<br />
<br />
In this BitNode:
<br />
<br />
Your hacking stat and experience gain are halved
<br />
The starting and maximum amount of money available on servers is significantly decreased
<br />
The growth rate of servers is significantly reduced
<br />
Weakening a server is twice as effective
<br />
Company wages are decreased by 50%
<br />
Corporation valuations are 90% lower and are therefore significantly less profitable
<br />
Hacknet Node production is significantly decreased
<br />
Crime and Infiltration are more lucrative
<br />
Augmentations are twice as expensive
<br />
<br />
Destroying this BitNode will give you Source-File 11, or if you already have this Source-File it will upgrade its
level up to a maximum of 3. This Source-File makes it so that company favor increases BOTH the player's salary and
reputation gain rate at that company by 1% per favor (rather than just the reputation gain). This Source-File also
@ -550,14 +429,6 @@ BitNodes["BitNode13"] = new BitNode(
other. Find her in {CityName.Chongqing} and gain her trust.
<br />
<br />
In this BitNode:
<br />
<br />
Every stat is significantly reduced
<br />
Stanek's Gift power is significantly increased.
<br />
<br />
Destroying this BitNode will give you Source-File 13, or if you already have this Source-File it will upgrade its
level up to a maximum of 3. This Source-File lets the {FactionNames.ChurchOfTheMachineGod} appear in other
BitNodes.
@ -567,372 +438,433 @@ BitNodes["BitNode13"] = new BitNode(
</>
),
);
// Books: Frontera, Shiner
BitNodes["BitNode14"] = new BitNode(14, 2, "", "COMING SOON");
BitNodes["BitNode15"] = new BitNode(15, 2, "", "COMING SOON");
BitNodes["BitNode16"] = new BitNode(16, 2, "", "COMING SOON");
BitNodes["BitNode17"] = new BitNode(17, 2, "", "COMING SOON");
BitNodes["BitNode18"] = new BitNode(18, 2, "", "COMING SOON");
BitNodes["BitNode19"] = new BitNode(19, 2, "", "COMING SOON");
BitNodes["BitNode20"] = new BitNode(20, 2, "", "COMING SOON");
BitNodes["BitNode21"] = new BitNode(21, 2, "", "COMING SOON");
BitNodes["BitNode22"] = new BitNode(22, 2, "", "COMING SOON");
BitNodes["BitNode23"] = new BitNode(23, 2, "", "COMING SOON");
BitNodes["BitNode24"] = new BitNode(24, 2, "", "COMING SOON");
export function initBitNodeMultipliers(p: IPlayer): void {
if (p.bitNodeN == null) {
p.bitNodeN = 1;
}
for (const mult of Object.keys(BitNodeMultipliers)) {
if (BitNodeMultipliers.hasOwnProperty(mult)) {
BitNodeMultipliers[mult] = 1;
}
}
// Special case.
BitNodeMultipliers.StaneksGiftExtraSize = 0;
export const defaultMultipliers: IBitNodeMultipliers = {
HackingLevelMultiplier: 1,
StrengthLevelMultiplier: 1,
DefenseLevelMultiplier: 1,
DexterityLevelMultiplier: 1,
AgilityLevelMultiplier: 1,
CharismaLevelMultiplier: 1,
switch (p.bitNodeN) {
case 1: // Source Genesis (every multiplier is 1)
break;
case 2: // Rise of the Underworld
BitNodeMultipliers.HackingLevelMultiplier = 0.8;
BitNodeMultipliers.ServerGrowthRate = 0.8;
BitNodeMultipliers.ServerMaxMoney = 0.2;
BitNodeMultipliers.ServerStartingMoney = 0.4;
BitNodeMultipliers.CrimeMoney = 3;
BitNodeMultipliers.InfiltrationMoney = 3;
BitNodeMultipliers.FactionWorkRepGain = 0.5;
BitNodeMultipliers.FactionPassiveRepGain = 0;
BitNodeMultipliers.StaneksGiftPowerMultiplier = 2;
BitNodeMultipliers.StaneksGiftExtraSize = -6;
BitNodeMultipliers.PurchasedServerSoftcap = 1.3;
BitNodeMultipliers.CorporationSoftCap = 0.9;
BitNodeMultipliers.WorldDaemonDifficulty = 5;
break;
case 3: // Corporatocracy
BitNodeMultipliers.HackingLevelMultiplier = 0.8;
BitNodeMultipliers.RepToDonateToFaction = 0.5;
BitNodeMultipliers.AugmentationRepCost = 3;
BitNodeMultipliers.AugmentationMoneyCost = 3;
BitNodeMultipliers.ServerMaxMoney = 0.2;
BitNodeMultipliers.ServerStartingMoney = 0.2;
BitNodeMultipliers.ServerGrowthRate = 0.2;
BitNodeMultipliers.ScriptHackMoney = 0.2;
BitNodeMultipliers.CompanyWorkMoney = 0.25;
BitNodeMultipliers.CrimeMoney = 0.25;
BitNodeMultipliers.HacknetNodeMoney = 0.25;
BitNodeMultipliers.HomeComputerRamCost = 1.5;
BitNodeMultipliers.PurchasedServerCost = 2;
BitNodeMultipliers.StaneksGiftPowerMultiplier = 0.75;
BitNodeMultipliers.StaneksGiftExtraSize = -2;
BitNodeMultipliers.PurchasedServerSoftcap = 1.3;
BitNodeMultipliers.GangSoftcap = 0.9;
BitNodeMultipliers.WorldDaemonDifficulty = 2;
BitNodeMultipliers.GangUniqueAugs = 0.5;
break;
case 4: // The Singularity
BitNodeMultipliers.ServerMaxMoney = 0.15;
BitNodeMultipliers.ServerStartingMoney = 0.75;
BitNodeMultipliers.ScriptHackMoney = 0.2;
BitNodeMultipliers.CompanyWorkMoney = 0.1;
BitNodeMultipliers.CrimeMoney = 0.2;
BitNodeMultipliers.HacknetNodeMoney = 0.05;
BitNodeMultipliers.CompanyWorkExpGain = 0.5;
BitNodeMultipliers.ClassGymExpGain = 0.5;
BitNodeMultipliers.FactionWorkExpGain = 0.5;
BitNodeMultipliers.HackExpGain = 0.4;
BitNodeMultipliers.CrimeExpGain = 0.5;
BitNodeMultipliers.FactionWorkRepGain = 0.75;
BitNodeMultipliers.StaneksGiftPowerMultiplier = 1.5;
BitNodeMultipliers.StaneksGiftExtraSize = 0;
BitNodeMultipliers.PurchasedServerSoftcap = 1.2;
BitNodeMultipliers.WorldDaemonDifficulty = 3;
BitNodeMultipliers.GangUniqueAugs = 0.5;
break;
case 5: // Artificial intelligence
BitNodeMultipliers.ServerMaxMoney = 2;
BitNodeMultipliers.ServerStartingSecurity = 2;
BitNodeMultipliers.ServerStartingMoney = 0.5;
BitNodeMultipliers.ScriptHackMoney = 0.15;
BitNodeMultipliers.HacknetNodeMoney = 0.2;
BitNodeMultipliers.CrimeMoney = 0.5;
BitNodeMultipliers.InfiltrationRep = 1.5;
BitNodeMultipliers.InfiltrationMoney = 1.5;
BitNodeMultipliers.AugmentationMoneyCost = 2;
BitNodeMultipliers.HackExpGain = 0.5;
BitNodeMultipliers.CorporationValuation = 0.5;
BitNodeMultipliers.StaneksGiftPowerMultiplier = 1.3;
BitNodeMultipliers.StaneksGiftExtraSize = 0;
BitNodeMultipliers.PurchasedServerSoftcap = 1.2;
BitNodeMultipliers.WorldDaemonDifficulty = 1.5;
BitNodeMultipliers.GangUniqueAugs = 0.5;
break;
case 6: // Bladeburner
BitNodeMultipliers.HackingLevelMultiplier = 0.35;
BitNodeMultipliers.ServerMaxMoney = 0.4;
BitNodeMultipliers.ServerStartingMoney = 0.5;
BitNodeMultipliers.ServerStartingSecurity = 1.5;
BitNodeMultipliers.ScriptHackMoney = 0.75;
BitNodeMultipliers.CompanyWorkMoney = 0.5;
BitNodeMultipliers.CrimeMoney = 0.75;
BitNodeMultipliers.InfiltrationMoney = 0.75;
BitNodeMultipliers.CorporationValuation = 0.2;
BitNodeMultipliers.HacknetNodeMoney = 0.2;
BitNodeMultipliers.HackExpGain = 0.25;
BitNodeMultipliers.DaedalusAugsRequirement = 1.166; // Results in 35 Augs needed
BitNodeMultipliers.PurchasedServerSoftcap = 2;
BitNodeMultipliers.StaneksGiftPowerMultiplier = 0.5;
BitNodeMultipliers.StaneksGiftExtraSize = 2;
BitNodeMultipliers.GangSoftcap = 0.7;
BitNodeMultipliers.CorporationSoftCap = 0.9;
BitNodeMultipliers.WorldDaemonDifficulty = 2;
BitNodeMultipliers.GangUniqueAugs = 0.2;
break;
case 7: // Bladeburner 2079
BitNodeMultipliers.BladeburnerRank = 0.6;
BitNodeMultipliers.BladeburnerSkillCost = 2;
BitNodeMultipliers.AugmentationMoneyCost = 3;
BitNodeMultipliers.HackingLevelMultiplier = 0.35;
BitNodeMultipliers.ServerMaxMoney = 0.4;
BitNodeMultipliers.ServerStartingMoney = 0.5;
BitNodeMultipliers.ServerStartingSecurity = 1.5;
BitNodeMultipliers.ScriptHackMoney = 0.5;
BitNodeMultipliers.CompanyWorkMoney = 0.5;
BitNodeMultipliers.CrimeMoney = 0.75;
BitNodeMultipliers.InfiltrationMoney = 0.75;
BitNodeMultipliers.CorporationValuation = 0.2;
BitNodeMultipliers.HacknetNodeMoney = 0.2;
BitNodeMultipliers.HackExpGain = 0.25;
BitNodeMultipliers.FourSigmaMarketDataCost = 2;
BitNodeMultipliers.FourSigmaMarketDataApiCost = 2;
BitNodeMultipliers.DaedalusAugsRequirement = 1.166; // Results in 35 Augs needed
BitNodeMultipliers.PurchasedServerSoftcap = 2;
BitNodeMultipliers.StaneksGiftPowerMultiplier = 0.9;
BitNodeMultipliers.StaneksGiftExtraSize = -1;
BitNodeMultipliers.GangSoftcap = 0.7;
BitNodeMultipliers.CorporationSoftCap = 0.9;
BitNodeMultipliers.WorldDaemonDifficulty = 2;
BitNodeMultipliers.GangUniqueAugs = 0.2;
break;
case 8: // Ghost of Wall Street
BitNodeMultipliers.ScriptHackMoney = 0.3;
BitNodeMultipliers.ScriptHackMoneyGain = 0;
BitNodeMultipliers.ManualHackMoney = 0;
BitNodeMultipliers.CompanyWorkMoney = 0;
BitNodeMultipliers.CrimeMoney = 0;
BitNodeMultipliers.HacknetNodeMoney = 0;
BitNodeMultipliers.InfiltrationMoney = 0;
BitNodeMultipliers.RepToDonateToFaction = 0;
BitNodeMultipliers.CorporationValuation = 0;
BitNodeMultipliers.CodingContractMoney = 0;
BitNodeMultipliers.StaneksGiftExtraSize = -7;
BitNodeMultipliers.PurchasedServerSoftcap = 4;
BitNodeMultipliers.GangSoftcap = 0;
BitNodeMultipliers.CorporationSoftCap = 0;
BitNodeMultipliers.GangUniqueAugs = 0;
break;
case 9: // Hacktocracy
BitNodeMultipliers.HackingLevelMultiplier = 0.5;
BitNodeMultipliers.StrengthLevelMultiplier = 0.45;
BitNodeMultipliers.DefenseLevelMultiplier = 0.45;
BitNodeMultipliers.DexterityLevelMultiplier = 0.45;
BitNodeMultipliers.AgilityLevelMultiplier = 0.45;
BitNodeMultipliers.CharismaLevelMultiplier = 0.45;
BitNodeMultipliers.PurchasedServerLimit = 0;
BitNodeMultipliers.HomeComputerRamCost = 5;
BitNodeMultipliers.CrimeMoney = 0.5;
BitNodeMultipliers.ScriptHackMoney = 0.1;
BitNodeMultipliers.HackExpGain = 0.05;
BitNodeMultipliers.ServerStartingMoney = 0.1;
BitNodeMultipliers.ServerMaxMoney = 0.1;
BitNodeMultipliers.ServerStartingSecurity = 2.5;
BitNodeMultipliers.CorporationValuation = 0.5;
BitNodeMultipliers.FourSigmaMarketDataCost = 5;
BitNodeMultipliers.FourSigmaMarketDataApiCost = 4;
BitNodeMultipliers.BladeburnerRank = 0.9;
BitNodeMultipliers.BladeburnerSkillCost = 1.2;
BitNodeMultipliers.StaneksGiftPowerMultiplier = 0.5;
BitNodeMultipliers.StaneksGiftExtraSize = 2;
BitNodeMultipliers.GangSoftcap = 0.8;
BitNodeMultipliers.CorporationSoftCap = 0.7;
BitNodeMultipliers.WorldDaemonDifficulty = 2;
BitNodeMultipliers.GangUniqueAugs = 0.25;
break;
case 10: // Digital Carbon
BitNodeMultipliers.HackingLevelMultiplier = 0.35;
BitNodeMultipliers.StrengthLevelMultiplier = 0.4;
BitNodeMultipliers.DefenseLevelMultiplier = 0.4;
BitNodeMultipliers.DexterityLevelMultiplier = 0.4;
BitNodeMultipliers.AgilityLevelMultiplier = 0.4;
BitNodeMultipliers.CharismaLevelMultiplier = 0.4;
BitNodeMultipliers.CompanyWorkMoney = 0.5;
BitNodeMultipliers.CrimeMoney = 0.5;
BitNodeMultipliers.HacknetNodeMoney = 0.5;
BitNodeMultipliers.ManualHackMoney = 0.5;
BitNodeMultipliers.ScriptHackMoney = 0.5;
BitNodeMultipliers.CodingContractMoney = 0.5;
BitNodeMultipliers.InfiltrationMoney = 0.5;
BitNodeMultipliers.CorporationValuation = 0.5;
BitNodeMultipliers.AugmentationMoneyCost = 5;
BitNodeMultipliers.AugmentationRepCost = 2;
BitNodeMultipliers.HomeComputerRamCost = 1.5;
BitNodeMultipliers.PurchasedServerCost = 5;
BitNodeMultipliers.PurchasedServerLimit = 0.6;
BitNodeMultipliers.PurchasedServerMaxRam = 0.5;
BitNodeMultipliers.BladeburnerRank = 0.8;
BitNodeMultipliers.StaneksGiftPowerMultiplier = 0.75;
BitNodeMultipliers.StaneksGiftExtraSize = -3;
BitNodeMultipliers.PurchasedServerSoftcap = 1.1;
BitNodeMultipliers.GangSoftcap = 0.9;
BitNodeMultipliers.CorporationSoftCap = 0.9;
BitNodeMultipliers.WorldDaemonDifficulty = 2;
BitNodeMultipliers.GangUniqueAugs = 0.25;
break;
case 11: //The Big Crash
BitNodeMultipliers.HackingLevelMultiplier = 0.6;
BitNodeMultipliers.HackExpGain = 0.5;
BitNodeMultipliers.ServerMaxMoney = 0.1;
BitNodeMultipliers.ServerStartingMoney = 0.1;
BitNodeMultipliers.ServerGrowthRate = 0.2;
BitNodeMultipliers.ServerWeakenRate = 2;
BitNodeMultipliers.CrimeMoney = 3;
BitNodeMultipliers.CompanyWorkMoney = 0.5;
BitNodeMultipliers.HacknetNodeMoney = 0.1;
BitNodeMultipliers.AugmentationMoneyCost = 2;
BitNodeMultipliers.InfiltrationMoney = 2.5;
BitNodeMultipliers.InfiltrationRep = 2.5;
BitNodeMultipliers.CorporationValuation = 0.1;
BitNodeMultipliers.CodingContractMoney = 0.25;
BitNodeMultipliers.FourSigmaMarketDataCost = 4;
BitNodeMultipliers.FourSigmaMarketDataApiCost = 4;
BitNodeMultipliers.PurchasedServerSoftcap = 2;
BitNodeMultipliers.CorporationSoftCap = 0.9;
BitNodeMultipliers.WorldDaemonDifficulty = 1.5;
BitNodeMultipliers.GangUniqueAugs = 0.75;
break;
ServerGrowthRate: 1,
ServerMaxMoney: 1,
ServerStartingMoney: 1,
ServerStartingSecurity: 1,
ServerWeakenRate: 1,
HomeComputerRamCost: 1,
PurchasedServerCost: 1,
PurchasedServerSoftcap: 1,
PurchasedServerLimit: 1,
PurchasedServerMaxRam: 1,
CompanyWorkMoney: 1,
CrimeMoney: 1,
HacknetNodeMoney: 1,
ManualHackMoney: 1,
ScriptHackMoney: 1,
ScriptHackMoneyGain: 1,
CodingContractMoney: 1,
ClassGymExpGain: 1,
CompanyWorkExpGain: 1,
CrimeExpGain: 1,
FactionWorkExpGain: 1,
HackExpGain: 1,
FactionPassiveRepGain: 1,
FactionWorkRepGain: 1,
RepToDonateToFaction: 1,
AugmentationMoneyCost: 1,
AugmentationRepCost: 1,
InfiltrationMoney: 1,
InfiltrationRep: 1,
FourSigmaMarketDataCost: 1,
FourSigmaMarketDataApiCost: 1,
CorporationValuation: 1,
CorporationSoftCap: 1,
BladeburnerRank: 1,
BladeburnerSkillCost: 1,
GangSoftcap: 1,
GangUniqueAugs: 1,
DaedalusAugsRequirement: 30,
StaneksGiftPowerMultiplier: 1,
StaneksGiftExtraSize: 0,
WorldDaemonDifficulty: 1,
};
export function getBitNodeMultipliers(n: number, lvl: number): IBitNodeMultipliers {
const mults = Object.assign({}, defaultMultipliers);
switch (n) {
case 1: {
return mults;
}
case 2: {
return Object.assign(mults, {
HackingLevelMultiplier: 0.8,
ServerGrowthRate: 0.8,
ServerMaxMoney: 0.2,
ServerStartingMoney: 0.4,
CrimeMoney: 3,
InfiltrationMoney: 3,
FactionWorkRepGain: 0.5,
FactionPassiveRepGain: 0,
StaneksGiftPowerMultiplier: 2,
StaneksGiftExtraSize: -6,
PurchasedServerSoftcap: 1.3,
CorporationSoftCap: 0.9,
WorldDaemonDifficulty: 5,
});
}
case 3: {
return Object.assign(mults, {
HackingLevelMultiplier: 0.8,
RepToDonateToFaction: 0.5,
AugmentationRepCost: 3,
AugmentationMoneyCost: 3,
ServerMaxMoney: 0.2,
ServerStartingMoney: 0.2,
ServerGrowthRate: 0.2,
ScriptHackMoney: 0.2,
CompanyWorkMoney: 0.25,
CrimeMoney: 0.25,
HacknetNodeMoney: 0.25,
HomeComputerRamCost: 1.5,
PurchasedServerCost: 2,
StaneksGiftPowerMultiplier: 0.75,
StaneksGiftExtraSize: -2,
PurchasedServerSoftcap: 1.3,
GangSoftcap: 0.9,
WorldDaemonDifficulty: 2,
GangUniqueAugs: 0.5,
});
}
case 4: {
return Object.assign(mults, {
ServerMaxMoney: 0.15,
ServerStartingMoney: 0.75,
ScriptHackMoney: 0.2,
CompanyWorkMoney: 0.1,
CrimeMoney: 0.2,
HacknetNodeMoney: 0.05,
CompanyWorkExpGain: 0.5,
ClassGymExpGain: 0.5,
FactionWorkExpGain: 0.5,
HackExpGain: 0.4,
CrimeExpGain: 0.5,
FactionWorkRepGain: 0.75,
StaneksGiftPowerMultiplier: 1.5,
StaneksGiftExtraSize: 0,
PurchasedServerSoftcap: 1.2,
WorldDaemonDifficulty: 3,
GangUniqueAugs: 0.5,
});
}
case 5: {
return Object.assign(mults, {
ServerMaxMoney: 2,
ServerStartingSecurity: 2,
ServerStartingMoney: 0.5,
ScriptHackMoney: 0.15,
HacknetNodeMoney: 0.2,
CrimeMoney: 0.5,
InfiltrationRep: 1.5,
InfiltrationMoney: 1.5,
AugmentationMoneyCost: 2,
HackExpGain: 0.5,
CorporationValuation: 0.5,
StaneksGiftPowerMultiplier: 1.3,
StaneksGiftExtraSize: 0,
PurchasedServerSoftcap: 1.2,
WorldDaemonDifficulty: 1.5,
GangUniqueAugs: 0.5,
});
}
case 6: {
return Object.assign(mults, {
HackingLevelMultiplier: 0.35,
ServerMaxMoney: 0.4,
ServerStartingMoney: 0.5,
ServerStartingSecurity: 1.5,
ScriptHackMoney: 0.75,
CompanyWorkMoney: 0.5,
CrimeMoney: 0.75,
InfiltrationMoney: 0.75,
CorporationValuation: 0.2,
HacknetNodeMoney: 0.2,
HackExpGain: 0.25,
DaedalusAugsRequirement: 35,
PurchasedServerSoftcap: 2,
StaneksGiftPowerMultiplier: 0.5,
StaneksGiftExtraSize: 2,
GangSoftcap: 0.7,
CorporationSoftCap: 0.9,
WorldDaemonDifficulty: 2,
GangUniqueAugs: 0.2,
});
}
case 7: {
return Object.assign(mults, {
BladeburnerRank: 0.6,
BladeburnerSkillCost: 2,
AugmentationMoneyCost: 3,
HackingLevelMultiplier: 0.35,
ServerMaxMoney: 0.4,
ServerStartingMoney: 0.5,
ServerStartingSecurity: 1.5,
ScriptHackMoney: 0.5,
CompanyWorkMoney: 0.5,
CrimeMoney: 0.75,
InfiltrationMoney: 0.75,
CorporationValuation: 0.2,
HacknetNodeMoney: 0.2,
HackExpGain: 0.25,
FourSigmaMarketDataCost: 2,
FourSigmaMarketDataApiCost: 2,
DaedalusAugsRequirement: 35,
PurchasedServerSoftcap: 2,
StaneksGiftPowerMultiplier: 0.9,
StaneksGiftExtraSize: -1,
GangSoftcap: 0.7,
CorporationSoftCap: 0.9,
WorldDaemonDifficulty: 2,
GangUniqueAugs: 0.2,
});
}
case 8: {
return Object.assign(mults, {
ScriptHackMoney: 0.3,
ScriptHackMoneyGain: 0,
ManualHackMoney: 0,
CompanyWorkMoney: 0,
CrimeMoney: 0,
HacknetNodeMoney: 0,
InfiltrationMoney: 0,
RepToDonateToFaction: 0,
CorporationValuation: 0,
CodingContractMoney: 0,
StaneksGiftExtraSize: -99,
PurchasedServerSoftcap: 4,
GangSoftcap: 0,
CorporationSoftCap: 0,
GangUniqueAugs: 0,
});
}
case 9: {
return Object.assign(mults, {
HackingLevelMultiplier: 0.5,
StrengthLevelMultiplier: 0.45,
DefenseLevelMultiplier: 0.45,
DexterityLevelMultiplier: 0.45,
AgilityLevelMultiplier: 0.45,
CharismaLevelMultiplier: 0.45,
PurchasedServerLimit: 0,
HomeComputerRamCost: 5,
CrimeMoney: 0.5,
ScriptHackMoney: 0.1,
HackExpGain: 0.05,
ServerStartingMoney: 0.1,
ServerMaxMoney: 0.1,
ServerStartingSecurity: 2.5,
CorporationValuation: 0.5,
FourSigmaMarketDataCost: 5,
FourSigmaMarketDataApiCost: 4,
BladeburnerRank: 0.9,
BladeburnerSkillCost: 1.2,
StaneksGiftPowerMultiplier: 0.5,
StaneksGiftExtraSize: 2,
GangSoftcap: 0.8,
CorporationSoftCap: 0.7,
WorldDaemonDifficulty: 2,
GangUniqueAugs: 0.25,
});
}
case 10: {
return Object.assign(mults, {
HackingLevelMultiplier: 0.35,
StrengthLevelMultiplier: 0.4,
DefenseLevelMultiplier: 0.4,
DexterityLevelMultiplier: 0.4,
AgilityLevelMultiplier: 0.4,
CharismaLevelMultiplier: 0.4,
CompanyWorkMoney: 0.5,
CrimeMoney: 0.5,
HacknetNodeMoney: 0.5,
ManualHackMoney: 0.5,
ScriptHackMoney: 0.5,
CodingContractMoney: 0.5,
InfiltrationMoney: 0.5,
CorporationValuation: 0.5,
AugmentationMoneyCost: 5,
AugmentationRepCost: 2,
HomeComputerRamCost: 1.5,
PurchasedServerCost: 5,
PurchasedServerLimit: 0.6,
PurchasedServerMaxRam: 0.5,
BladeburnerRank: 0.8,
StaneksGiftPowerMultiplier: 0.75,
StaneksGiftExtraSize: -3,
PurchasedServerSoftcap: 1.1,
GangSoftcap: 0.9,
CorporationSoftCap: 0.9,
WorldDaemonDifficulty: 2,
GangUniqueAugs: 0.25,
});
}
case 11: {
return Object.assign(mults, {
HackingLevelMultiplier: 0.6,
HackExpGain: 0.5,
ServerMaxMoney: 0.1,
ServerStartingMoney: 0.1,
ServerGrowthRate: 0.2,
ServerWeakenRate: 2,
CrimeMoney: 3,
CompanyWorkMoney: 0.5,
HacknetNodeMoney: 0.1,
AugmentationMoneyCost: 2,
InfiltrationMoney: 2.5,
InfiltrationRep: 2.5,
CorporationValuation: 0.1,
CodingContractMoney: 0.25,
FourSigmaMarketDataCost: 4,
FourSigmaMarketDataApiCost: 4,
PurchasedServerSoftcap: 2,
CorporationSoftCap: 0.9,
WorldDaemonDifficulty: 1.5,
GangUniqueAugs: 0.75,
});
}
case 12: {
//The Recursion
let sf12Lvl = 0;
for (let i = 0; i < p.sourceFiles.length; i++) {
if (p.sourceFiles[i].n === 12) {
sf12Lvl = p.sourceFiles[i].lvl;
}
}
const inc = Math.pow(1.02, sf12Lvl);
const inc = Math.pow(1.02, lvl);
const dec = 1 / inc;
// Multiplier for number of augs needed for Daedalus increases
// up to a maximum of 1.34, which results in 40 Augs required
BitNodeMultipliers.DaedalusAugsRequirement = Math.min(inc, 1.34);
return Object.assign(mults, {
DaedalusAugsRequirement: Math.floor(Math.min(mults.DaedalusAugsRequirement + inc, 40)),
BitNodeMultipliers.HackingLevelMultiplier = dec;
BitNodeMultipliers.StrengthLevelMultiplier = dec;
BitNodeMultipliers.DefenseLevelMultiplier = dec;
BitNodeMultipliers.DexterityLevelMultiplier = dec;
BitNodeMultipliers.AgilityLevelMultiplier = dec;
BitNodeMultipliers.CharismaLevelMultiplier = dec;
HackingLevelMultiplier: dec,
StrengthLevelMultiplier: dec,
DefenseLevelMultiplier: dec,
DexterityLevelMultiplier: dec,
AgilityLevelMultiplier: dec,
CharismaLevelMultiplier: dec,
BitNodeMultipliers.ServerMaxMoney = dec;
BitNodeMultipliers.ServerStartingMoney = dec;
BitNodeMultipliers.ServerGrowthRate = dec;
BitNodeMultipliers.ServerWeakenRate = dec;
ServerMaxMoney: dec,
ServerStartingMoney: dec,
ServerGrowthRate: dec,
ServerWeakenRate: dec,
//Does not scale, otherwise security might start at 300+
BitNodeMultipliers.ServerStartingSecurity = 1.5;
ServerStartingSecurity: 1.5,
BitNodeMultipliers.HomeComputerRamCost = inc;
HomeComputerRamCost: inc,
BitNodeMultipliers.PurchasedServerCost = inc;
BitNodeMultipliers.PurchasedServerLimit = dec;
BitNodeMultipliers.PurchasedServerMaxRam = dec;
BitNodeMultipliers.PurchasedServerSoftcap = inc;
PurchasedServerCost: inc,
PurchasedServerLimit: dec,
PurchasedServerMaxRam: dec,
PurchasedServerSoftcap: inc,
BitNodeMultipliers.ManualHackMoney = dec;
BitNodeMultipliers.ScriptHackMoney = dec;
BitNodeMultipliers.CompanyWorkMoney = dec;
BitNodeMultipliers.CrimeMoney = dec;
BitNodeMultipliers.HacknetNodeMoney = dec;
BitNodeMultipliers.CodingContractMoney = dec;
ManualHackMoney: dec,
ScriptHackMoney: dec,
CompanyWorkMoney: dec,
CrimeMoney: dec,
HacknetNodeMoney: dec,
CodingContractMoney: dec,
BitNodeMultipliers.CompanyWorkExpGain = dec;
BitNodeMultipliers.ClassGymExpGain = dec;
BitNodeMultipliers.FactionWorkExpGain = dec;
BitNodeMultipliers.HackExpGain = dec;
BitNodeMultipliers.CrimeExpGain = dec;
CompanyWorkExpGain: dec,
ClassGymExpGain: dec,
FactionWorkExpGain: dec,
HackExpGain: dec,
CrimeExpGain: dec,
BitNodeMultipliers.FactionWorkRepGain = dec;
BitNodeMultipliers.FactionPassiveRepGain = dec;
BitNodeMultipliers.RepToDonateToFaction = inc;
FactionWorkRepGain: dec,
FactionPassiveRepGain: dec,
RepToDonateToFaction: inc,
BitNodeMultipliers.AugmentationRepCost = inc;
BitNodeMultipliers.AugmentationMoneyCost = inc;
AugmentationRepCost: inc,
AugmentationMoneyCost: inc,
BitNodeMultipliers.InfiltrationMoney = dec;
BitNodeMultipliers.InfiltrationRep = dec;
InfiltrationMoney: dec,
InfiltrationRep: dec,
BitNodeMultipliers.FourSigmaMarketDataCost = inc;
BitNodeMultipliers.FourSigmaMarketDataApiCost = inc;
FourSigmaMarketDataCost: inc,
FourSigmaMarketDataApiCost: inc,
BitNodeMultipliers.CorporationValuation = dec;
CorporationValuation: dec,
BitNodeMultipliers.BladeburnerRank = dec;
BitNodeMultipliers.BladeburnerSkillCost = inc;
BladeburnerRank: dec,
BladeburnerSkillCost: inc,
BitNodeMultipliers.StaneksGiftPowerMultiplier = inc;
BitNodeMultipliers.StaneksGiftExtraSize = inc;
BitNodeMultipliers.GangSoftcap = 0.8;
BitNodeMultipliers.CorporationSoftCap = 0.8;
BitNodeMultipliers.WorldDaemonDifficulty = inc;
StaneksGiftPowerMultiplier: inc,
StaneksGiftExtraSize: inc,
GangSoftcap: 0.8,
CorporationSoftCap: 0.8,
WorldDaemonDifficulty: inc,
BitNodeMultipliers.GangUniqueAugs = dec;
break;
GangUniqueAugs: dec,
});
}
case 13: {
BitNodeMultipliers.PurchasedServerSoftcap = 1.6;
return Object.assign(mults, {
PurchasedServerSoftcap: 1.6,
BitNodeMultipliers.HackingLevelMultiplier = 0.25;
BitNodeMultipliers.StrengthLevelMultiplier = 0.7;
BitNodeMultipliers.DefenseLevelMultiplier = 0.7;
BitNodeMultipliers.DexterityLevelMultiplier = 0.7;
BitNodeMultipliers.AgilityLevelMultiplier = 0.7;
HackingLevelMultiplier: 0.25,
StrengthLevelMultiplier: 0.7,
DefenseLevelMultiplier: 0.7,
DexterityLevelMultiplier: 0.7,
AgilityLevelMultiplier: 0.7,
BitNodeMultipliers.ServerMaxMoney = 0.45;
BitNodeMultipliers.ServerStartingMoney = 0.75;
ServerMaxMoney: 0.45,
ServerStartingMoney: 0.75,
BitNodeMultipliers.ServerStartingSecurity = 3;
ServerStartingSecurity: 3,
BitNodeMultipliers.ScriptHackMoney = 0.2;
BitNodeMultipliers.CompanyWorkMoney = 0.4;
BitNodeMultipliers.CrimeMoney = 0.4;
BitNodeMultipliers.HacknetNodeMoney = 0.4;
BitNodeMultipliers.CodingContractMoney = 0.4;
ScriptHackMoney: 0.2,
CompanyWorkMoney: 0.4,
CrimeMoney: 0.4,
HacknetNodeMoney: 0.4,
CodingContractMoney: 0.4,
BitNodeMultipliers.CompanyWorkExpGain = 0.5;
BitNodeMultipliers.ClassGymExpGain = 0.5;
BitNodeMultipliers.FactionWorkExpGain = 0.5;
BitNodeMultipliers.HackExpGain = 0.1;
BitNodeMultipliers.CrimeExpGain = 0.5;
CompanyWorkExpGain: 0.5,
ClassGymExpGain: 0.5,
FactionWorkExpGain: 0.5,
HackExpGain: 0.1,
CrimeExpGain: 0.5,
BitNodeMultipliers.FactionWorkRepGain = 0.6;
FactionWorkRepGain: 0.6,
BitNodeMultipliers.FourSigmaMarketDataCost = 10;
BitNodeMultipliers.FourSigmaMarketDataApiCost = 10;
FourSigmaMarketDataCost: 10,
FourSigmaMarketDataApiCost: 10,
BitNodeMultipliers.CorporationValuation = 0.001;
CorporationValuation: 0.001,
BitNodeMultipliers.BladeburnerRank = 0.45;
BitNodeMultipliers.BladeburnerSkillCost = 2;
BitNodeMultipliers.StaneksGiftPowerMultiplier = 2;
BitNodeMultipliers.StaneksGiftExtraSize = 1;
BitNodeMultipliers.GangSoftcap = 0.3;
BitNodeMultipliers.CorporationSoftCap = 0.3;
BitNodeMultipliers.WorldDaemonDifficulty = 3;
BitNodeMultipliers.GangUniqueAugs = 0.1;
break;
BladeburnerRank: 0.45,
BladeburnerSkillCost: 2,
StaneksGiftPowerMultiplier: 2,
StaneksGiftExtraSize: 1,
GangSoftcap: 0.3,
CorporationSoftCap: 0.3,
WorldDaemonDifficulty: 3,
GangUniqueAugs: 0.1,
});
}
default:
console.warn("Player.bitNodeN invalid");
break;
default: {
throw new Error("Invalid BitNodeN");
}
}
}
export function initBitNodeMultipliers(p: IPlayer): void {
Object.assign(BitNodeMultipliers, getBitNodeMultipliers(p.bitNodeN, p.sourceFileLvl(p.bitNodeN)));
}

@ -1,9 +1,11 @@
import { defaultMultipliers } from "./BitNode";
/**
* Bitnode multipliers influence the difficulty of different aspects of the game.
* Each Bitnode has a different theme/strategy to achieving the end goal, so these multipliers will can help drive the
* player toward the intended strategy. Unless they really want to play the long, slow game of waiting...
*/
interface IBitNodeMultipliers {
export interface IBitNodeMultipliers {
/**
* Influences how quickly the player's agility level (not exp) scales
*/
@ -250,67 +252,4 @@ interface IBitNodeMultipliers {
* The multipliers that are influenced by current Bitnode progression.
*/
// tslint:disable-next-line:variable-name
export const BitNodeMultipliers: IBitNodeMultipliers = {
HackingLevelMultiplier: 1,
StrengthLevelMultiplier: 1,
DefenseLevelMultiplier: 1,
DexterityLevelMultiplier: 1,
AgilityLevelMultiplier: 1,
CharismaLevelMultiplier: 1,
ServerGrowthRate: 1,
ServerMaxMoney: 1,
ServerStartingMoney: 1,
ServerStartingSecurity: 1,
ServerWeakenRate: 1,
HomeComputerRamCost: 1,
PurchasedServerCost: 1,
PurchasedServerSoftcap: 1,
PurchasedServerLimit: 1,
PurchasedServerMaxRam: 1,
CompanyWorkMoney: 1,
CrimeMoney: 1,
HacknetNodeMoney: 1,
ManualHackMoney: 1,
ScriptHackMoney: 1,
ScriptHackMoneyGain: 1,
CodingContractMoney: 1,
ClassGymExpGain: 1,
CompanyWorkExpGain: 1,
CrimeExpGain: 1,
FactionWorkExpGain: 1,
HackExpGain: 1,
FactionPassiveRepGain: 1,
FactionWorkRepGain: 1,
RepToDonateToFaction: 1,
AugmentationMoneyCost: 1,
AugmentationRepCost: 1,
InfiltrationMoney: 1,
InfiltrationRep: 1,
FourSigmaMarketDataCost: 1,
FourSigmaMarketDataApiCost: 1,
CorporationValuation: 1,
CorporationSoftCap: 1,
BladeburnerRank: 1,
BladeburnerSkillCost: 1,
GangSoftcap: 1,
GangUniqueAugs: 1,
DaedalusAugsRequirement: 1,
StaneksGiftPowerMultiplier: 1,
StaneksGiftExtraSize: 0,
WorldDaemonDifficulty: 1,
};
export const BitNodeMultipliers = defaultMultipliers;

@ -0,0 +1,554 @@
import ExpandMore from "@mui/icons-material/ExpandMore";
import ExpandLess from "@mui/icons-material/ExpandLess";
import { Box, Collapse, ListItemButton, ListItemText, Paper, Typography } from "@mui/material";
import React from "react";
import { use } from "../../ui/Context";
import { defaultMultipliers, getBitNodeMultipliers } from "../BitNode";
import { IBitNodeMultipliers } from "../BitNodeMultipliers";
import { SpecialServers } from "../../Server/data/SpecialServers";
interface IProps {
n: number;
}
export function BitnodeMultiplierDescription({ n }: IProps): React.ReactElement {
const player = use.Player();
const [open, setOpen] = React.useState(false);
const mults = getBitNodeMultipliers(n, player.sourceFileLvl(n));
if (n === 1) return <></>;
return (
<>
<br />
<Box component={Paper}>
<ListItemButton onClick={() => setOpen((old) => !old)}>
<ListItemText primary={<Typography>Bitnode multipliers:</Typography>} />
{open ? <ExpandLess color="primary" /> : <ExpandMore color="primary" />}
</ListItemButton>
<Box mx={2}>
<Collapse in={open}>
<GeneralMults n={n} mults={mults} />
<FactionMults n={n} mults={mults} />
<AugmentationMults n={n} mults={mults} />
<StockMults n={n} mults={mults} />
<SkillMults n={n} mults={mults} />
<HackingMults n={n} mults={mults} />
<PurchasedServersMults n={n} mults={mults} />
<CrimeMults n={n} mults={mults} />
<InfiltrationMults n={n} mults={mults} />
<CompanyMults n={n} mults={mults} />
<GangMults n={n} mults={mults} />
<CorporationMults n={n} mults={mults} />
<BladeburnerMults n={n} mults={mults} />
<StanekMults n={n} mults={mults} />
<br />
</Collapse>
</Box>
</Box>
</>
);
}
interface IMultsProps {
n: number;
mults: IBitNodeMultipliers;
}
function GeneralMults({ mults }: IMultsProps): React.ReactElement {
// is it empty check
if (
mults.ClassGymExpGain === defaultMultipliers.ClassGymExpGain &&
mults.CodingContractMoney === defaultMultipliers.CodingContractMoney &&
mults.DaedalusAugsRequirement === defaultMultipliers.DaedalusAugsRequirement &&
mults.WorldDaemonDifficulty === defaultMultipliers.WorldDaemonDifficulty &&
mults.HacknetNodeMoney === defaultMultipliers.HacknetNodeMoney
)
return <></>;
return (
<>
<br />
<Typography variant={"h5"}>General:</Typography>
<Box mx={1}>
{mults.WorldDaemonDifficulty !== defaultMultipliers.WorldDaemonDifficulty ? (
<Typography>
{SpecialServers.WorldDaemon} difficulty: x{mults.WorldDaemonDifficulty.toFixed(3)}
</Typography>
) : (
<></>
)}
{mults.DaedalusAugsRequirement !== defaultMultipliers.DaedalusAugsRequirement ? (
<Typography>Daedalus aug req.: {mults.DaedalusAugsRequirement}</Typography>
) : (
<></>
)}
{mults.HacknetNodeMoney !== defaultMultipliers.HacknetNodeMoney ? (
<Typography>Hacknet production: x{mults.HacknetNodeMoney.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.CodingContractMoney !== defaultMultipliers.CodingContractMoney ? (
<Typography>Coding contract reward: x{mults.CodingContractMoney.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.ClassGymExpGain !== defaultMultipliers.ClassGymExpGain ? (
<Typography>Class/Gym exp: x{mults.ClassGymExpGain.toFixed(3)}</Typography>
) : (
<></>
)}
</Box>
</>
);
}
function AugmentationMults({ mults }: IMultsProps): React.ReactElement {
// is it empty check
if (
mults.AugmentationMoneyCost === defaultMultipliers.AugmentationMoneyCost &&
mults.AugmentationRepCost === defaultMultipliers.AugmentationRepCost
)
return <></>;
return (
<>
<br />
<Typography variant={"h5"}>Augmentations:</Typography>
<Box mx={1}>
{mults.AugmentationMoneyCost !== defaultMultipliers.AugmentationMoneyCost ? (
<Typography>Cost: x{mults.AugmentationMoneyCost.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.AugmentationRepCost !== defaultMultipliers.AugmentationRepCost ? (
<Typography>Reputation: x{mults.AugmentationRepCost.toFixed(3)}</Typography>
) : (
<></>
)}
</Box>
</>
);
}
function CompanyMults({ mults }: IMultsProps): React.ReactElement {
// is it empty check
if (
mults.CompanyWorkExpGain === defaultMultipliers.CompanyWorkExpGain &&
mults.CompanyWorkMoney === defaultMultipliers.CompanyWorkMoney
)
return <></>;
return (
<>
<br />
<Typography variant={"h5"}>Company:</Typography>
<Box mx={1}>
{mults.CompanyWorkMoney !== defaultMultipliers.CompanyWorkMoney ? (
<Typography>Money: x{mults.CompanyWorkMoney.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.CompanyWorkExpGain !== defaultMultipliers.CompanyWorkExpGain ? (
<Typography>Exp: x{mults.CompanyWorkExpGain.toFixed(3)}</Typography>
) : (
<></>
)}
</Box>
</>
);
}
function StockMults({ mults }: IMultsProps): React.ReactElement {
// is it empty check
if (
mults.FourSigmaMarketDataApiCost === defaultMultipliers.FourSigmaMarketDataApiCost &&
mults.FourSigmaMarketDataCost === defaultMultipliers.FourSigmaMarketDataCost
)
return <></>;
return (
<>
<br />
<Typography variant={"h5"}>Stock market:</Typography>
<Box mx={1}>
{mults.FourSigmaMarketDataCost !== defaultMultipliers.FourSigmaMarketDataCost ? (
<Typography>Market data cost: x{mults.FourSigmaMarketDataCost.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.FourSigmaMarketDataApiCost !== defaultMultipliers.FourSigmaMarketDataApiCost ? (
<Typography>Market data API cost: x{mults.FourSigmaMarketDataApiCost.toFixed(3)}</Typography>
) : (
<></>
)}
</Box>
</>
);
}
function FactionMults({ mults }: IMultsProps): React.ReactElement {
// is it empty check
if (
mults.FactionPassiveRepGain === defaultMultipliers.FactionPassiveRepGain &&
mults.FactionWorkExpGain === defaultMultipliers.FactionWorkExpGain &&
mults.FactionWorkRepGain === defaultMultipliers.FactionWorkRepGain &&
mults.RepToDonateToFaction === defaultMultipliers.RepToDonateToFaction
)
return <></>;
return (
<>
<br />
<Typography variant={"h5"}>Faction:</Typography>
<Box mx={1}>
{mults.RepToDonateToFaction !== defaultMultipliers.RepToDonateToFaction ? (
<Typography>Favor to donate: x{mults.RepToDonateToFaction.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.FactionWorkRepGain !== defaultMultipliers.FactionWorkRepGain ? (
<Typography>Work rep: x{mults.FactionWorkRepGain.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.FactionWorkExpGain !== defaultMultipliers.FactionWorkExpGain ? (
<Typography>Work exp: x{mults.FactionWorkExpGain.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.FactionPassiveRepGain !== defaultMultipliers.FactionPassiveRepGain ? (
<Typography>Passive rep: x{mults.FactionPassiveRepGain.toFixed(3)}</Typography>
) : (
<></>
)}
</Box>
</>
);
}
function CrimeMults({ mults }: IMultsProps): React.ReactElement {
// is it empty check
if (mults.CrimeExpGain === defaultMultipliers.CrimeExpGain && mults.CrimeMoney === defaultMultipliers.CrimeMoney)
return <></>;
return (
<>
<br />
<Typography variant={"h5"}>Crime:</Typography>
<Box mx={1}>
{mults.CrimeExpGain !== defaultMultipliers.CrimeExpGain ? (
<Typography>Exp: x{mults.CrimeExpGain.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.CrimeMoney !== defaultMultipliers.CrimeMoney ? (
<Typography>Money: x{mults.CrimeMoney.toFixed(3)}</Typography>
) : (
<></>
)}
</Box>
</>
);
}
function SkillMults({ mults }: IMultsProps): React.ReactElement {
// is it empty check
if (
mults.HackingLevelMultiplier === defaultMultipliers.HackingLevelMultiplier &&
mults.AgilityLevelMultiplier === defaultMultipliers.AgilityLevelMultiplier &&
mults.DefenseLevelMultiplier === defaultMultipliers.DefenseLevelMultiplier &&
mults.DexterityLevelMultiplier === defaultMultipliers.DexterityLevelMultiplier &&
mults.StrengthLevelMultiplier === defaultMultipliers.StrengthLevelMultiplier &&
mults.CharismaLevelMultiplier === defaultMultipliers.CharismaLevelMultiplier
)
return <></>;
return (
<>
<br />
<Typography variant={"h5"}>Skills:</Typography>
<Box mx={1}>
{mults.HackingLevelMultiplier !== defaultMultipliers.HackingLevelMultiplier ? (
<Typography>Hacking: x{mults.HackingLevelMultiplier.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.AgilityLevelMultiplier !== defaultMultipliers.AgilityLevelMultiplier ? (
<Typography>Agility: x{mults.AgilityLevelMultiplier.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.DefenseLevelMultiplier !== defaultMultipliers.DefenseLevelMultiplier ? (
<Typography>Defense: x{mults.DefenseLevelMultiplier.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.DexterityLevelMultiplier !== defaultMultipliers.DexterityLevelMultiplier ? (
<Typography>Dexterity: x{mults.DexterityLevelMultiplier.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.StrengthLevelMultiplier !== defaultMultipliers.StrengthLevelMultiplier ? (
<Typography>Strength: x{mults.StrengthLevelMultiplier.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.CharismaLevelMultiplier !== defaultMultipliers.CharismaLevelMultiplier ? (
<Typography>Charisma: x{mults.CharismaLevelMultiplier.toFixed(3)}</Typography>
) : (
<></>
)}
</Box>
</>
);
}
function HackingMults({ mults }: IMultsProps): React.ReactElement {
// is it empty check
if (
mults.ServerGrowthRate === defaultMultipliers.ServerGrowthRate &&
mults.ServerMaxMoney === defaultMultipliers.ServerMaxMoney &&
mults.ServerStartingMoney === defaultMultipliers.ServerStartingMoney &&
mults.ServerStartingSecurity === defaultMultipliers.ServerStartingSecurity &&
mults.ServerWeakenRate === defaultMultipliers.ServerWeakenRate &&
mults.ManualHackMoney === defaultMultipliers.ManualHackMoney &&
mults.ScriptHackMoney === defaultMultipliers.ScriptHackMoney &&
mults.ScriptHackMoneyGain === defaultMultipliers.ScriptHackMoneyGain &&
mults.HackExpGain === defaultMultipliers.HackExpGain
)
return <></>;
return (
<>
<br />
<Typography variant={"h5"}>Hacking:</Typography>
<Box mx={1}>
{mults.HackExpGain !== defaultMultipliers.HackExpGain ? (
<Typography>Exp: x{mults.HackExpGain.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.ServerGrowthRate !== defaultMultipliers.ServerGrowthRate ? (
<Typography>Growth rate: x{mults.ServerGrowthRate.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.ServerMaxMoney !== defaultMultipliers.ServerMaxMoney ? (
<Typography>Max money: x{mults.ServerMaxMoney.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.ServerStartingMoney !== defaultMultipliers.ServerStartingMoney ? (
<Typography>Starting money: x{mults.ServerStartingMoney.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.ServerStartingSecurity !== defaultMultipliers.ServerStartingSecurity ? (
<Typography>Starting security: x{mults.ServerStartingSecurity.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.ServerWeakenRate !== defaultMultipliers.ServerWeakenRate ? (
<Typography>Weaken rate: x{mults.ServerWeakenRate.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.ManualHackMoney !== defaultMultipliers.ManualHackMoney ? (
<Typography>Manual hack money: x{mults.ManualHackMoney.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.ScriptHackMoney !== defaultMultipliers.ScriptHackMoney ? (
<Typography>Hack money stolen: x{mults.ScriptHackMoney.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.ScriptHackMoneyGain !== defaultMultipliers.ScriptHackMoneyGain ? (
<Typography>Money gained from hack: x{mults.ScriptHackMoneyGain.toFixed(3)}</Typography>
) : (
<></>
)}
</Box>
</>
);
}
function PurchasedServersMults({ mults }: IMultsProps): React.ReactElement {
// is it empty check
if (
mults.PurchasedServerCost === defaultMultipliers.PurchasedServerCost &&
mults.PurchasedServerSoftcap === defaultMultipliers.PurchasedServerSoftcap &&
mults.PurchasedServerLimit === defaultMultipliers.PurchasedServerLimit &&
mults.PurchasedServerMaxRam === defaultMultipliers.PurchasedServerMaxRam &&
mults.HomeComputerRamCost === defaultMultipliers.HomeComputerRamCost
)
return <></>;
return (
<>
<br />
<Typography variant={"h5"}>Purchased servers:</Typography>
<Box mx={1}>
{mults.PurchasedServerCost !== defaultMultipliers.PurchasedServerCost ? (
<Typography>Base cost: {mults.PurchasedServerCost.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.PurchasedServerSoftcap !== defaultMultipliers.PurchasedServerSoftcap ? (
<Typography>Softcap cost: {mults.PurchasedServerSoftcap.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.PurchasedServerLimit !== defaultMultipliers.PurchasedServerLimit ? (
<Typography>Limit: x{mults.PurchasedServerLimit.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.PurchasedServerMaxRam !== defaultMultipliers.PurchasedServerMaxRam ? (
<Typography>Max ram: x{mults.PurchasedServerMaxRam.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.HomeComputerRamCost !== defaultMultipliers.HomeComputerRamCost ? (
<Typography>Home ram cost: x{mults.HomeComputerRamCost.toFixed(3)}</Typography>
) : (
<></>
)}
</Box>
</>
);
}
function InfiltrationMults({ mults }: IMultsProps): React.ReactElement {
// is it empty check
if (
mults.InfiltrationMoney === defaultMultipliers.InfiltrationMoney &&
mults.InfiltrationRep === defaultMultipliers.InfiltrationRep
)
return <></>;
return (
<>
<br />
<Typography variant={"h5"}>Infiltration:</Typography>
<Box mx={1}>
{mults.InfiltrationMoney !== defaultMultipliers.InfiltrationMoney ? (
<Typography>Money: {mults.InfiltrationMoney.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.InfiltrationRep !== defaultMultipliers.InfiltrationRep ? (
<Typography>Reputation: x{mults.InfiltrationRep.toFixed(3)}</Typography>
) : (
<></>
)}
</Box>
</>
);
}
function BladeburnerMults({ n, mults }: IMultsProps): React.ReactElement {
const player = use.Player();
// access check
if (n !== 6 && n !== 7 && player.sourceFileLvl(6) === 0) return <></>;
//default mults check
if (mults.BladeburnerRank === 1 && mults.BladeburnerSkillCost === 1) return <></>;
return (
<>
<br />
<Typography variant={"h5"}>Bladeburner:</Typography>
<Box mx={1}>
{mults.BladeburnerRank !== 1 ? <Typography>Rank gain: x{mults.BladeburnerRank.toFixed(3)}</Typography> : <></>}
{mults.BladeburnerSkillCost !== 1 ? (
<Typography>Skill cost: x{mults.BladeburnerSkillCost.toFixed(3)}</Typography>
) : (
<></>
)}
</Box>
</>
);
}
function StanekMults({ n, mults }: IMultsProps): React.ReactElement {
const player = use.Player();
// access check
if (n !== 13 && player.sourceFileLvl(13) === 0) return <></>;
//default mults check
if (
mults.StaneksGiftExtraSize === defaultMultipliers.StaneksGiftExtraSize &&
mults.StaneksGiftPowerMultiplier === defaultMultipliers.StaneksGiftPowerMultiplier
)
return <></>;
const s = mults.StaneksGiftExtraSize;
return (
<>
<br />
<Typography variant={"h5"}>Stanek's Gift:</Typography>
<Box mx={1}>
{mults.StaneksGiftPowerMultiplier !== defaultMultipliers.StaneksGiftPowerMultiplier ? (
<Typography>Gift power: x{mults.StaneksGiftPowerMultiplier.toFixed(3)}</Typography>
) : (
<></>
)}
{s !== defaultMultipliers.StaneksGiftExtraSize ? (
<Typography>Base size modifier: {s > defaultMultipliers.StaneksGiftExtraSize ? `+${s}` : s}</Typography>
) : (
<></>
)}
</Box>
</>
);
}
function GangMults({ n, mults }: IMultsProps): React.ReactElement {
const player = use.Player();
// access check
if (n !== 2 && player.sourceFileLvl(2) === 0) return <></>;
// is it empty check
if (
mults.GangSoftcap === defaultMultipliers.GangSoftcap &&
mults.GangUniqueAugs === defaultMultipliers.GangUniqueAugs
)
return <></>;
return (
<>
<br />
<Typography variant={"h5"}>Gang:</Typography>
<Box mx={1}>
{mults.GangSoftcap !== defaultMultipliers.GangSoftcap ? (
<Typography>Softcap: {mults.GangSoftcap.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.GangUniqueAugs !== defaultMultipliers.GangUniqueAugs ? (
<Typography>Unique augs: x{mults.GangUniqueAugs.toFixed(3)}</Typography>
) : (
<></>
)}
</Box>
</>
);
}
function CorporationMults({ n, mults }: IMultsProps): React.ReactElement {
const player = use.Player();
// access check
if (n !== 3 && player.sourceFileLvl(3) === 0) return <></>;
// is it empty check
if (
mults.CorporationSoftCap === defaultMultipliers.CorporationSoftCap &&
mults.CorporationValuation === defaultMultipliers.CorporationValuation
)
return <></>;
return (
<>
<br />
<Typography variant={"h5"}>Corporation:</Typography>
<Box mx={1}>
{mults.CorporationSoftCap !== defaultMultipliers.CorporationSoftCap ? (
<Typography>Softcap: {mults.CorporationSoftCap.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.CorporationValuation !== defaultMultipliers.CorporationValuation ? (
<Typography>Valuation: x{mults.CorporationValuation.toFixed(3)}</Typography>
) : (
<></>
)}
</Box>
</>
);
}

@ -163,7 +163,9 @@ export function BitverseRoot(props: IProps): React.ReactElement {
return lvl;
}
const max = n === 12 ? Infinity : 3;
return Math.min(max, lvl + 1);
// If accessed via flume, display the current BN level, else the next
return Math.min(max, lvl + Number(!props.flume));
};
if (Settings.DisableASCIIArt) {
@ -171,7 +173,6 @@ export function BitverseRoot(props: IProps): React.ReactElement {
<>
{Object.values(BitNodes)
.filter((node) => {
console.log(node.desc);
return node.desc !== "COMING SOON";
})
.map((node) => {

@ -6,6 +6,7 @@ import { use } from "../../ui/Context";
import { Modal } from "../../ui/React/Modal";
import Typography from "@mui/material/Typography";
import Button from "@mui/material/Button";
import { BitnodeMultiplierDescription } from "./BitnodeMultipliersDescription";
interface IProps {
open: boolean;
@ -40,6 +41,7 @@ export function PortalModal(props: IProps): React.ReactElement {
<br />
<br />
<Typography>{bitNode.info}</Typography>
<BitnodeMultiplierDescription n={props.n} />
<br />
<br />
<Button

@ -112,13 +112,15 @@ export const CONSTANTS: {
CodingContractBaseMoneyGain: number;
AugmentationGraftingCostMult: number;
AugmentationGraftingTimeBase: number;
SoACostMult: number;
SoARepMult: number;
EntropyEffect: number;
TotalNumBitNodes: number;
Donations: number; // number of blood/plasma/palette donation the dev have verified., boosts NFG
LatestUpdate: string;
} = {
VersionString: "1.6.4",
VersionNumber: 14,
VersionNumber: 15,
// Speed (in ms) at which the main loop is updated
_idleSpeed: 200,
@ -281,13 +283,17 @@ export const CONSTANTS: {
AugmentationGraftingCostMult: 3,
AugmentationGraftingTimeBase: 3600000,
// SoA mults
SoACostMult: 7,
SoARepMult: 1.3,
// Value raised to the number of entropy stacks, then multiplied to player multipliers
EntropyEffect: 0.98,
// BitNode/Source-File related stuff
TotalNumBitNodes: 24,
Donations: 2,
Donations: 6,
LatestUpdate: `
v1.6.3 - 2022-04-01 Few stanek fixes

@ -3,7 +3,6 @@ import { Augmentation } from "../Augmentation/Augmentation";
import { PlayerOwnedAugmentation } from "../Augmentation/PlayerOwnedAugmentation";
import { AugmentationNames } from "../Augmentation/data/AugmentationNames";
import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers";
import { CONSTANTS } from "../Constants";
import { Faction } from "./Faction";
import { Factions } from "./Factions";
@ -19,6 +18,7 @@ import {
import { dialogBoxCreate } from "../ui/React/DialogBox";
import { InvitationEvent } from "./ui/InvitationModal";
import { FactionNames } from "./data/FactionNames";
import { updateAugmentationCosts, getNextNeuroFluxLevel } from "../Augmentation/AugmentationHelpers";
import { SFC32RNG } from "../Casino/RNG";
export function inviteToFaction(faction: Faction): void {
@ -104,31 +104,13 @@ export function purchaseAugmentation(aug: Augmentation, fac: Faction, sing = fal
} else if (aug.baseCost === 0 || Player.money >= aug.baseCost) {
const queuedAugmentation = new PlayerOwnedAugmentation(aug.name);
if (aug.name == AugmentationNames.NeuroFluxGovernor) {
queuedAugmentation.level = getNextNeurofluxLevel();
queuedAugmentation.level = getNextNeuroFluxLevel();
}
Player.queuedAugmentations.push(queuedAugmentation);
Player.loseMoney(aug.baseCost, "augmentations");
// If you just purchased Neuroflux Governor, recalculate the cost
if (aug.name == AugmentationNames.NeuroFluxGovernor) {
let nextLevel = getNextNeurofluxLevel();
--nextLevel;
const mult = Math.pow(CONSTANTS.NeuroFluxGovernorLevelMult, nextLevel);
aug.baseRepRequirement = 500 * mult * BitNodeMultipliers.AugmentationRepCost;
aug.baseCost = 750e3 * mult * BitNodeMultipliers.AugmentationMoneyCost;
for (let i = 0; i < Player.queuedAugmentations.length - 1; ++i) {
aug.baseCost *= CONSTANTS.MultipleAugMultiplier * [1, 0.96, 0.94, 0.93][Player.sourceFileLvl(11)];
}
}
for (const name of Object.keys(Augmentations)) {
if (Augmentations.hasOwnProperty(name)) {
Augmentations[name].baseCost *=
CONSTANTS.MultipleAugMultiplier * [1, 0.96, 0.94, 0.93][Player.sourceFileLvl(11)];
}
}
updateAugmentationCosts();
if (sing) {
return "You purchased " + aug.name;
@ -152,24 +134,6 @@ export function purchaseAugmentation(aug: Augmentation, fac: Faction, sing = fal
return "";
}
export function getNextNeurofluxLevel(): number {
// Get current Neuroflux level based on Player's augmentations
let currLevel = 0;
for (let i = 0; i < Player.augmentations.length; ++i) {
if (Player.augmentations[i].name === AugmentationNames.NeuroFluxGovernor) {
currLevel = Player.augmentations[i].level;
}
}
// Account for purchased but uninstalled Augmentations
for (let i = 0; i < Player.queuedAugmentations.length; ++i) {
if (Player.queuedAugmentations[i].name == AugmentationNames.NeuroFluxGovernor) {
++currLevel;
}
}
return currLevel + 1;
}
export function processPassiveFactionRepGain(numCycles: number): void {
for (const name of Object.keys(Factions)) {
if (name === Player.currentWorkFactionName) continue;

@ -3,6 +3,7 @@ import { IMap } from "../types";
import { FactionNames } from "./data/FactionNames";
import { use } from "../ui/Context";
import { Option } from "./ui/Option";
import { Typography } from "@mui/material";
interface FactionInfoParams {
infoText?: JSX.Element;
@ -511,4 +512,17 @@ export const FactionInfos: IMap<FactionInfo> = {
);
},
}),
[FactionNames.ShadowsOfAnarchy]: new FactionInfo({
infoText: (
<>
The government is ruled by the corporations that we have allowed to consume it. To release the world from its
shackles, the gods grant us their strength.
</>
),
special: true,
keepOnInstall: true,
assignment: (): React.ReactElement => {
return <Typography>{FactionNames.ShadowsOfAnarchy} can only gain reputation by infiltrating.</Typography>;
},
}),
};

@ -32,4 +32,5 @@ export enum FactionNames {
CyberSec = "CyberSec",
Bladeburners = "Bladeburners",
ChurchOfTheMachineGod = "Church of the Machine God",
ShadowsOfAnarchy = "Shadows of Anarchy",
}

@ -24,6 +24,7 @@ import Tooltip from "@mui/material/Tooltip";
import TableBody from "@mui/material/TableBody";
import Table from "@mui/material/Table";
import { getGenericAugmentationPriceMultiplier } from "../../Augmentation/AugmentationHelpers";
import { FactionNames } from "../data/FactionNames";
type IProps = {
faction: Faction;
@ -164,6 +165,14 @@ export function AugmentationsPage(props: IProps): React.ReactElement {
</>
);
}
const multiplierComponent =
props.faction.name !== FactionNames.ShadowsOfAnarchy ? (
<Typography>
Price multiplier: x {numeralWrapper.formatMultiplier(getGenericAugmentationPriceMultiplier())}
</Typography>
) : (
<></>
);
return (
<>
@ -185,9 +194,7 @@ export function AugmentationsPage(props: IProps): React.ReactElement {
</Typography>
}
>
<Typography>
Price multiplier: x {numeralWrapper.formatMultiplier(getGenericAugmentationPriceMultiplier())}
</Typography>
{multiplierComponent}
</Tooltip>
</Box>
<Button onClick={() => switchSortOrder(PurchaseAugmentationsOrderSetting.Cost)}>Sort by Cost</Button>

@ -18,11 +18,11 @@ import { Faction } from "../Faction";
import { use } from "../../ui/Context";
import { CreateGangModal } from "./CreateGangModal";
import Typography from "@mui/material/Typography";
import Button from "@mui/material/Button";
import { Box, Paper, Typography, Button, Tooltip } from "@mui/material";
import { CovenantPurchasesRoot } from "../../PersonObjects/Sleeve/ui/CovenantPurchasesRoot";
import { FactionNames } from "../data/FactionNames";
import { GangConstants } from "../../Gang/data/Constants";
import { GangButton } from "./GangButton";
type IProps = {
faction: Faction;
@ -62,18 +62,8 @@ function MainPage({ faction, rerender, onAugmentations }: IMainProps): React.Rea
const player = use.Player();
const router = use.Router();
const [sleevesOpen, setSleevesOpen] = useState(false);
const [gangOpen, setGangOpen] = useState(false);
const factionInfo = faction.getInfo();
function manageGang(): void {
// If player already has a gang, just go to the gang UI
if (player.inGang()) {
return router.toGang();
}
setGangOpen(true);
}
function startWork(): void {
player.startFocusing();
router.toWork();
@ -105,15 +95,6 @@ function MainPage({ faction, rerender, onAugmentations }: IMainProps): React.Rea
const canPurchaseSleeves = faction.name === FactionNames.TheCovenant && player.bitNodeN === 10;
let canAccessGang = player.canAccessGang() && GangConstants.Names.includes(faction.name);
if (player.inGang()) {
if (player.getGangName() !== faction.name) {
canAccessGang = false;
} else if (player.getGangName() === faction.name) {
canAccessGang = true;
}
}
return (
<>
<Button onClick={() => router.toFactions()}>Back</Button>
@ -121,12 +102,7 @@ function MainPage({ faction, rerender, onAugmentations }: IMainProps): React.Rea
{faction.name}
</Typography>
<Info faction={faction} factionInfo={factionInfo} />
{canAccessGang && (
<>
<Option buttonText={"Manage Gang"} infoText={gangInfo} onClick={manageGang} />
<CreateGangModal facName={faction.name} open={gangOpen} onClose={() => setGangOpen(false)} />
</>
)}
<GangButton faction={faction} />
{!isPlayersGang && factionInfo.offerHackingWork && (
<Option
buttonText={"Hacking Contracts"}

@ -0,0 +1,79 @@
import { Button, Typography, Box, Paper, Tooltip } from "@mui/material";
import React, { useState } from "react";
import { GangConstants } from "../../Gang/data/Constants";
import { use } from "../../ui/Context";
import { Faction } from "../Faction";
import { CreateGangModal } from "./CreateGangModal";
type IProps = {
faction: Faction;
};
export function GangButton({ faction }: IProps): React.ReactElement {
const player = use.Player();
const router = use.Router();
const [gangOpen, setGangOpen] = useState(false);
if (
!GangConstants.Names.includes(faction.name) || // not even a gang
!player.isAwareOfGang() || // doesn't know about gang
(player.inGang() && player.getGangName() !== faction.name) // already in another gang
) {
return <></>;
}
let data = {
enabled: false,
title: "",
tooltip: "" as string | React.ReactElement,
description: "",
};
if (player.inGang()) {
data = {
enabled: true,
title: "Manage Gang",
tooltip: "",
description: "Manage a gang for this Faction. Gangs will earn you money and faction reputation",
};
} else {
data = {
enabled: player.canAccessGang(),
title: "Create Gang",
tooltip: !player.canAccessGang() ? (
<Typography>Unlocked when reaching {GangConstants.GangKarmaRequirement} karma</Typography>
) : (
""
),
description: "Create a gang for this Faction. Gangs will earn you money and faction reputation",
};
}
const manageGang = (): void => {
// If player already has a gang, just go to the gang UI
if (player.inGang()) {
return router.toGang();
}
setGangOpen(true);
};
return (
<>
<Box>
<Paper sx={{ my: 1, p: 1 }}>
<Tooltip title={data.tooltip}>
<span>
<Button onClick={manageGang} disabled={!data.enabled}>
{data.title}
</Button>
</span>
</Tooltip>
<Typography>{data.description}</Typography>
</Paper>
</Box>
<CreateGangModal facName={faction.name} open={gangOpen} onClose={() => setGangOpen(false)} />
</>
);
}

@ -58,6 +58,7 @@ export function Info(props: IProps): React.ReactElement {
const Assignment = props.factionInfo.assignment ?? DefaultAssignment;
const favorGain = props.faction.getFavorGain();
return (
<>
<Typography classes={{ root: classes.noformat }}>{props.factionInfo.infoText}</Typography>

@ -4,7 +4,7 @@
*/
import React, { useState } from "react";
import { getNextNeurofluxLevel, hasAugmentationPrereqs, purchaseAugmentation } from "../FactionHelpers";
import { hasAugmentationPrereqs, purchaseAugmentation } from "../FactionHelpers";
import { PurchaseAugmentationModal } from "./PurchaseAugmentationModal";
import { Augmentations } from "../../Augmentation/Augmentations";
@ -22,6 +22,7 @@ import Tooltip from "@mui/material/Tooltip";
import Box from "@mui/material/Box";
import { TableCell } from "../../ui/React/Table";
import TableRow from "@mui/material/TableRow";
import { getNextNeuroFluxLevel } from "../../Augmentation/AugmentationHelpers";
interface IReqProps {
augName: string;
@ -96,7 +97,7 @@ export function PurchaseableAugmentation(props: IProps): React.ReactElement {
// Determine button txt
let btnTxt = aug.name;
if (aug.name === AugmentationNames.NeuroFluxGovernor) {
btnTxt += ` - Level ${getNextNeurofluxLevel()}`;
btnTxt += ` - Level ${getNextNeuroFluxLevel()}`;
}
let tooltip = <></>;

@ -0,0 +1,6 @@
export enum GameOptionsTab {
SYSTEM,
INTERFACE,
GAMEPLAY,
MISC,
}

@ -0,0 +1,356 @@
import { MenuItem, Select, SelectChangeEvent, TextField, Tooltip, Typography } from "@mui/material";
import React, { useState } from "react";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { Settings } from "../../Settings/Settings";
import { OptionSwitch } from "../../ui/React/OptionSwitch";
import { formatTime } from "../../utils/helpers/formatTime";
import { GameOptionsTab } from "../GameOptionsTab";
import { GameOptionsPage } from "./GameOptionsPage";
import { OptionsSlider } from "./OptionsSlider";
interface IProps {
currentTab: GameOptionsTab;
player: IPlayer;
}
export const CurrentOptionsPage = (props: IProps): React.ReactElement => {
const [execTime, setExecTime] = useState(Settings.CodeInstructionRunTime);
const [recentScriptsSize, setRecentScriptsSize] = useState(Settings.MaxRecentScriptsCapacity);
const [logSize, setLogSize] = useState(Settings.MaxLogCapacity);
const [portSize, setPortSize] = useState(Settings.MaxPortCapacity);
const [terminalSize, setTerminalSize] = useState(Settings.MaxTerminalCapacity);
const [autosaveInterval, setAutosaveInterval] = useState(Settings.AutosaveInterval);
const [timestampFormat, setTimestampFormat] = useState(Settings.TimestampsFormat);
const [locale, setLocale] = useState(Settings.Locale);
function handleExecTimeChange(event: any, newValue: number | number[]): void {
setExecTime(newValue as number);
Settings.CodeInstructionRunTime = newValue as number;
}
function handleRecentScriptsSizeChange(event: any, newValue: number | number[]): void {
setRecentScriptsSize(newValue as number);
Settings.MaxRecentScriptsCapacity = newValue as number;
}
function handleLogSizeChange(event: any, newValue: number | number[]): void {
setLogSize(newValue as number);
Settings.MaxLogCapacity = newValue as number;
}
function handlePortSizeChange(event: any, newValue: number | number[]): void {
setPortSize(newValue as number);
Settings.MaxPortCapacity = newValue as number;
}
function handleTerminalSizeChange(event: any, newValue: number | number[]): void {
setTerminalSize(newValue as number);
Settings.MaxTerminalCapacity = newValue as number;
}
function handleAutosaveIntervalChange(event: any, newValue: number | number[]): void {
setAutosaveInterval(newValue as number);
Settings.AutosaveInterval = newValue as number;
}
function handleLocaleChange(event: SelectChangeEvent<string>): void {
setLocale(event.target.value as string);
Settings.Locale = event.target.value as string;
}
function handleTimestampFormatChange(event: React.ChangeEvent<HTMLInputElement>): void {
setTimestampFormat(event.target.value);
Settings.TimestampsFormat = event.target.value;
}
const pages = {
[GameOptionsTab.SYSTEM]: (
<GameOptionsPage title="System">
{/* Wrap in a React fragment to prevent the sliders from breaking as list items */}
<>
<OptionsSlider
label=".script exec time (ms)"
value={execTime}
callback={handleExecTimeChange}
step={1}
min={5}
max={100}
tooltip={
<>
The minimum number of milliseconds it takes to execute an operation in Netscript. Setting this too low
can result in poor performance if you have many scripts running.
</>
}
/>
<OptionsSlider
label="Recently killed scripts size"
value={recentScriptsSize}
callback={handleRecentScriptsSizeChange}
step={25}
min={0}
max={500}
tooltip={
<>
The maximum number of lines a script's logs can hold. Setting this too high can cause the game to use a
lot of memory if you have many scripts running.
</>
}
/>
<OptionsSlider
label="Netscript log size"
value={logSize}
callback={handleLogSizeChange}
step={20}
min={20}
max={500}
tooltip={
<>
The maximum number of lines a script's logs can hold. Setting this too high can cause the game to use a
lot of memory if you have many scripts running.
</>
}
/>
<OptionsSlider
label="Netscript port size"
value={portSize}
callback={handlePortSizeChange}
step={1}
min={20}
max={100}
tooltip={
<>
The maximum number of entries that can be written to a port using Netscript's write() function. Setting
this too high can cause the game to use a lot of memory.
</>
}
/>
<OptionsSlider
label="Terminal capacity"
value={terminalSize}
callback={handleTerminalSizeChange}
step={50}
min={50}
max={500}
tooltip={
<>
The maximum number of entries that can be written to the terminal. Setting this too high can cause the
game to use a lot of memory.
</>
}
marks
/>
<OptionsSlider
label="Autosave interval (s)"
value={autosaveInterval}
callback={handleAutosaveIntervalChange}
step={30}
min={0}
max={600}
tooltip={<>The time (in seconds) between each autosave. Set to 0 to disable autosave.</>}
marks
/>
</>
<OptionSwitch
checked={Settings.SuppressSavedGameToast}
onChange={(newValue) => (Settings.SuppressSavedGameToast = newValue)}
text="Suppress Auto-Save Game Toast"
tooltip={<>If this is set, there will be no "Game Saved!" toast appearing after an auto-save.</>}
/>
<OptionSwitch
checked={Settings.SuppressAutosaveDisabledWarnings}
onChange={(newValue) => (Settings.SuppressAutosaveDisabledWarnings = newValue)}
text="Suppress Auto-Save Disabled Warning"
tooltip={<>If this is set, there will be no warning triggered when auto-save is disabled (at 0).</>}
/>
<OptionSwitch
checked={Settings.SaveGameOnFileSave}
onChange={(newValue) => (Settings.SaveGameOnFileSave = newValue)}
text="Save game on file save"
tooltip={<>Save your game any time a file is saved in the script editor.</>}
/>
<OptionSwitch
checked={Settings.ExcludeRunningScriptsFromSave}
onChange={(newValue) => (Settings.ExcludeRunningScriptsFromSave = newValue)}
text="Exclude Running Scripts from Save"
tooltip={
<>
If this is set, the save file will exclude all running scripts. This is only useful if your save is
lagging a lot. You'll have to restart your script every time you launch the game.
</>
}
/>
</GameOptionsPage>
),
[GameOptionsTab.INTERFACE]: (
<GameOptionsPage title="Interface">
<OptionSwitch
checked={Settings.DisableASCIIArt}
onChange={(newValue) => (Settings.DisableASCIIArt = newValue)}
text="Disable ascii art"
tooltip={<>If this is set all ASCII art will be disabled.</>}
/>
<OptionSwitch
checked={Settings.DisableTextEffects}
onChange={(newValue) => (Settings.DisableTextEffects = newValue)}
text="Disable text effects"
tooltip={
<>
If this is set, text effects will not be displayed. This can help if text is difficult to read in certain
areas.
</>
}
/>
<OptionSwitch
checked={Settings.DisableOverviewProgressBars}
onChange={(newValue) => (Settings.DisableOverviewProgressBars = newValue)}
text="Disable Overview Progress Bars"
tooltip={<>If this is set, the progress bars in the character overview will be hidden.</>}
/>
<OptionSwitch
checked={Settings.UseIEC60027_2}
onChange={(newValue) => (Settings.UseIEC60027_2 = newValue)}
text="Use GiB instead of GB"
tooltip={
<>If this is set all references to memory will use GiB instead of GB, in accordance with IEC 60027-2.</>
}
/>
<Tooltip
title={
<Typography>
Terminal commands and log entries will be timestamped. See https://date-fns.org/docs/Getting-Started/
</Typography>
}
>
<TextField
InputProps={{
startAdornment: (
<Typography
color={formatTime(timestampFormat) === "format error" && timestampFormat !== "" ? "error" : "success"}
>
Timestamp format:
</Typography>
),
}}
value={timestampFormat}
onChange={handleTimestampFormatChange}
placeholder="yyyy-MM-dd hh:mm:ss"
/>
</Tooltip>
<>
<Tooltip title={<Typography>Sets the locale for displaying numbers.</Typography>}>
<Typography>Locale</Typography>
</Tooltip>
<Select value={locale} onChange={handleLocaleChange}>
<MenuItem value="en">en</MenuItem>
<MenuItem value="bg">bg</MenuItem>
<MenuItem value="cs">cs</MenuItem>
<MenuItem value="da-dk">da-dk</MenuItem>
<MenuItem value="de">de</MenuItem>
<MenuItem value="en-au">en-au</MenuItem>
<MenuItem value="en-gb">en-gb</MenuItem>
<MenuItem value="es">es</MenuItem>
<MenuItem value="fr">fr</MenuItem>
<MenuItem value="hu">hu</MenuItem>
<MenuItem value="it">it</MenuItem>
<MenuItem value="lv">lv</MenuItem>
<MenuItem value="no">no</MenuItem>
<MenuItem value="pl">pl</MenuItem>
<MenuItem value="ru">ru</MenuItem>
</Select>
</>
</GameOptionsPage>
),
[GameOptionsTab.GAMEPLAY]: (
<GameOptionsPage title="Gameplay">
<OptionSwitch
checked={Settings.SuppressMessages}
onChange={(newValue) => (Settings.SuppressMessages = newValue)}
text="Suppress story messages"
tooltip={
<>
If this is set, then any messages you receive will not appear as popups on the screen. They will still get
sent to your home computer as '.msg' files and can be viewed with the 'cat' Terminal command.
</>
}
/>
<OptionSwitch
checked={Settings.SuppressFactionInvites}
onChange={(newValue) => (Settings.SuppressFactionInvites = newValue)}
text="Suppress faction invites"
tooltip={
<>
If this is set, then any faction invites you receive will not appear as popups on the screen. Your
outstanding faction invites can be viewed in the 'Factions' page.
</>
}
/>
<OptionSwitch
checked={Settings.SuppressTravelConfirmation}
onChange={(newValue) => (Settings.SuppressTravelConfirmation = newValue)}
text="Suppress travel confirmations"
tooltip={
<>
If this is set, the confirmation message before traveling will not show up. You will automatically be
deducted the travel cost as soon as you click.
</>
}
/>
<OptionSwitch
checked={Settings.SuppressBuyAugmentationConfirmation}
onChange={(newValue) => (Settings.SuppressBuyAugmentationConfirmation = newValue)}
text="Suppress augmentations confirmation"
tooltip={<>If this is set, the confirmation message before buying augmentation will not show up.</>}
/>
<OptionSwitch
checked={Settings.SuppressTIXPopup}
onChange={(newValue) => (Settings.SuppressTIXPopup = newValue)}
text="Suppress TIX messages"
tooltip={<>If this is set, the stock market will never create any popup.</>}
/>
{props.player.bladeburner && (
<OptionSwitch
checked={Settings.SuppressBladeburnerPopup}
onChange={(newValue) => (Settings.SuppressBladeburnerPopup = newValue)}
text="Suppress bladeburner popup"
tooltip={
<>
If this is set, then having your Bladeburner actions interrupted by being busy with something else will
not display a popup message.
</>
}
/>
)}
</GameOptionsPage>
),
[GameOptionsTab.MISC]: (
<GameOptionsPage title="Misc">
<OptionSwitch
checked={Settings.DisableHotkeys}
onChange={(newValue) => (Settings.DisableHotkeys = newValue)}
text="Disable hotkeys"
tooltip={
<>
If this is set, then most hotkeys (keyboard shortcuts) in the game are disabled. This includes Terminal
commands, hotkeys to navigate between different parts of the game, and the "Save and Close (Ctrl + b)"
hotkey in the Text Editor.
</>
}
/>
<OptionSwitch
checked={Settings.EnableBashHotkeys}
onChange={(newValue) => (Settings.EnableBashHotkeys = newValue)}
text="Enable bash hotkeys"
tooltip={
<>
Improved Bash emulation mode. Setting this to 1 enables several new Terminal shortcuts and features that
more closely resemble a real Bash-style shell. Note that when this mode is enabled, the default browser
shortcuts are overriden by the new Bash shortcuts.
</>
}
/>
</GameOptionsPage>
),
};
return pages[props.currentTab];
};

@ -0,0 +1,29 @@
import { List, ListItem, Paper, Typography } from "@mui/material";
import { uniqueId } from "lodash";
import React from "react";
interface IProps {
children: React.ReactElement | (React.ReactElement | null)[];
title: string;
}
export const GameOptionsPage = (props: IProps): React.ReactElement => {
return (
<Paper sx={{ height: "fit-content", p: 1 }}>
<Typography variant="h6">{props.title}</Typography>
{(props.children as any)?.length > 1 ? (
<List disablePadding dense>
{(props.children as React.ReactElement[])
.filter((c) => c)
.map((c, i) => (
<ListItem key={uniqueId(String(i))} sx={{ px: 0, display: "block" }}>
{c}
</ListItem>
))}
</List>
) : (
props.children
)}
</Paper>
);
};

@ -0,0 +1,39 @@
import { Box, Container, Typography } from "@mui/material";
import React, { useState } from "react";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { IRouter } from "../../ui/Router";
import { GameOptionsTab } from "../GameOptionsTab";
import { CurrentOptionsPage } from "./CurrentOptionsPage";
import { GameOptionsSidebar } from "./GameOptionsSidebar";
interface IProps {
player: IPlayer;
router: IRouter;
save: () => void;
export: () => void;
forceKill: () => void;
softReset: () => void;
}
export function GameOptionsRoot(props: IProps): React.ReactElement {
const [currentTab, setCurrentTab] = useState(GameOptionsTab.SYSTEM);
return (
<Container disableGutters maxWidth="lg" sx={{ mx: 0 }}>
<Typography variant="h4">Options</Typography>
<Box sx={{ display: "grid", gridTemplateColumns: "1fr 3fr", gap: 1 }}>
<GameOptionsSidebar
tab={currentTab}
setTab={(tab: GameOptionsTab) => setCurrentTab(tab)}
player={props.player}
router={props.router}
save={props.save}
export={props.export}
forceKill={props.forceKill}
softReset={props.softReset}
/>
<CurrentOptionsPage currentTab={currentTab} player={props.player} />
</Box>
</Container>
);
}

@ -0,0 +1,336 @@
import {
BugReport,
Chat,
Download,
LibraryBooks,
Palette,
Public,
Reddit,
Save,
SystemUpdateAlt,
Upload,
Bloodtype,
} from "@mui/icons-material";
import { Box, Button, List, ListItemButton, Paper, Tooltip, Typography } from "@mui/material";
import { default as React, useRef, useState } from "react";
import { FileDiagnosticModal } from "../../Diagnostic/FileDiagnosticModal";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { ImportData, saveObject } from "../../SaveObject";
import { Settings } from "../../Settings/Settings";
import { StyleEditorButton } from "../../Themes/ui/StyleEditorButton";
import { ThemeEditorButton } from "../../Themes/ui/ThemeEditorButton";
import { ConfirmationModal } from "../../ui/React/ConfirmationModal";
import { DeleteGameButton } from "../../ui/React/DeleteGameButton";
import { SnackbarEvents, ToastVariant } from "../../ui/React/Snackbar";
import { SoftResetButton } from "../../ui/React/SoftResetButton";
import { IRouter } from "../../ui/Router";
import { convertTimeMsToTimeElapsedString } from "../../utils/StringHelperFunctions";
import { GameOptionsTab } from "../GameOptionsTab";
interface IProps {
tab: GameOptionsTab;
setTab: (tab: GameOptionsTab) => void;
player: IPlayer;
router: IRouter;
save: () => void;
export: () => void;
forceKill: () => void;
softReset: () => void;
}
interface ITabProps {
sideBarProps: IProps;
tab: GameOptionsTab;
tabName: string;
}
const SideBarTab = (props: ITabProps): React.ReactElement => {
return (
<ListItemButton
selected={props.sideBarProps.tab === props.tab}
onClick={() => props.sideBarProps.setTab(props.tab)}
>
<Typography>{props.tabName}</Typography>
</ListItemButton>
);
};
export const GameOptionsSidebar = (props: IProps): React.ReactElement => {
const importInput = useRef<HTMLInputElement>(null);
const [diagnosticOpen, setDiagnosticOpen] = useState(false);
const [importSaveOpen, setImportSaveOpen] = useState(false);
const [importData, setImportData] = useState<ImportData | null>(null);
function startImport(): void {
if (!window.File || !window.FileReader || !window.FileList || !window.Blob) return;
const ii = importInput.current;
if (ii === null) throw new Error("import input should not be null");
ii.click();
}
async function onImport(event: React.ChangeEvent<HTMLInputElement>): Promise<void> {
try {
const base64Save = await saveObject.getImportStringFromFile(event.target.files);
const data = await saveObject.getImportDataFromString(base64Save);
setImportData(data);
setImportSaveOpen(true);
} catch (ex: any) {
SnackbarEvents.emit(ex.toString(), ToastVariant.ERROR, 5000);
}
}
async function confirmedImportGame(): Promise<void> {
if (!importData) return;
try {
await saveObject.importGame(importData.base64);
} catch (ex: any) {
SnackbarEvents.emit(ex.toString(), ToastVariant.ERROR, 5000);
}
setImportSaveOpen(false);
setImportData(null);
}
function compareSaveGame(): void {
if (!importData) return;
props.router.toImportSave(importData.base64);
setImportSaveOpen(false);
setImportData(null);
}
return (
<Box>
<Paper sx={{ height: "fit-content", mb: 1 }}>
<List>
<SideBarTab sideBarProps={props} tab={GameOptionsTab.SYSTEM} tabName="System" />
<SideBarTab sideBarProps={props} tab={GameOptionsTab.GAMEPLAY} tabName="Gameplay" />
<SideBarTab sideBarProps={props} tab={GameOptionsTab.INTERFACE} tabName="Interface" />
<SideBarTab sideBarProps={props} tab={GameOptionsTab.MISC} tabName="Misc" />
</List>
</Paper>
<Box
sx={{
display: "grid",
width: "100%",
height: "fit-content",
gridTemplateAreas: `"save delete"
"export import"
"kill kill"
"reset diagnose"
"browse browse"
"theme style"
"links links"
"devs devs"`,
gridTemplateColumns: "1fr 1fr",
}}
>
<Button onClick={() => props.save()} startIcon={<Save />} sx={{ gridArea: "save" }}>
Save Game
</Button>
<Box sx={{ gridArea: "delete", "& .MuiButton-root": { height: "100%", width: "100%" } }}>
<DeleteGameButton />
</Box>
<Tooltip title={<Typography>Export your game to a text file.</Typography>}>
<Button onClick={() => props.export()} startIcon={<Download />} sx={{ gridArea: "export" }}>
Export Game
</Button>
</Tooltip>
<Tooltip
title={
<Typography>
Import your game from a text file.
<br />
This will <strong>overwrite</strong> your current game. Back it up first!
</Typography>
}
>
<Button onClick={startImport} startIcon={<Upload />} sx={{ gridArea: "import" }}>
Import Game
<input ref={importInput} id="import-game-file-selector" type="file" hidden onChange={onImport} />
</Button>
</Tooltip>
<ConfirmationModal
open={importSaveOpen}
onClose={() => setImportSaveOpen(false)}
onConfirm={() => confirmedImportGame()}
additionalButton={<Button onClick={compareSaveGame}>Compare Save</Button>}
confirmationText={
<>
Importing a new game will <strong>completely wipe</strong> the current data!
<br />
<br />
Make sure to have a backup of your current save file before importing.
<br />
The file you are attempting to import seems valid.
{(importData?.playerData?.lastSave ?? 0) > 0 && (
<>
<br />
<br />
The export date of the save file is{" "}
<strong>{new Date(importData?.playerData?.lastSave ?? 0).toLocaleString()}</strong>
</>
)}
{(importData?.playerData?.totalPlaytime ?? 0) > 0 && (
<>
<br />
<br />
Total play time of imported game:{" "}
{convertTimeMsToTimeElapsedString(importData?.playerData?.totalPlaytime ?? 0)}
</>
)}
<br />
<br />
</>
}
/>
<Tooltip
title={
<Typography>
Forcefully kill all active running scripts, in case there is a bug or some unexpected issue with the game.
After using this, save the game and then reload the page. This is different then normal kill in that
normal kill will tell the script to shut down while force kill just removes the references to it (and it
should crash on it's own). This will not remove the files on your computer. Just forcefully kill all
running instances of all scripts.
</Typography>
}
>
<Button onClick={() => props.forceKill()} sx={{ gridArea: "kill" }}>
Force kill all active scripts
</Button>
</Tooltip>
<Box sx={{ gridArea: "reset", "& .MuiButton-root": { height: "100%", width: "100%" } }}>
<SoftResetButton
noConfirmation={Settings.SuppressBuyAugmentationConfirmation}
onTriggered={props.softReset}
/>
</Box>
<Tooltip
title={
<Typography>
If your save file is extremely big you can use this button to view a map of all the files on every server.
Be careful there might be spoilers.
</Typography>
}
>
<Button onClick={() => setDiagnosticOpen(true)} sx={{ gridArea: "diagnose" }}>
Diagnose files
</Button>
</Tooltip>
<Tooltip title="Head to the theme browser to see a collection of prebuilt themes.">
<Button startIcon={<Palette />} onClick={() => props.router.toThemeBrowser()} sx={{ gridArea: "browse" }}>
Theme Browser
</Button>
</Tooltip>
<Box sx={{ gridArea: "theme", "& .MuiButton-root": { height: "100%", width: "100%" } }}>
<ThemeEditorButton router={props.router} />
</Box>
<Box sx={{ gridArea: "style", "& .MuiButton-root": { height: "100%", width: "100%" } }}>
<StyleEditorButton />
</Box>
<Box
sx={{
gridArea: "links",
display: "grid",
gridTemplateAreas: `"bug changelog"
"docs docs"
"discord reddit"
"plaza plaza"`,
gridTemplateColumns: "1fr 1fr",
my: 1,
}}
>
<Button
startIcon={<BugReport />}
href="https://github.com/danielyxie/bitburner/issues/new"
target="_blank"
sx={{ gridArea: "bug" }}
>
Report Bug
</Button>
<Button
startIcon={<SystemUpdateAlt />}
href="https://bitburner.readthedocs.io/en/latest/changelog.html"
target="_blank"
sx={{ gridArea: " changelog" }}
>
Changelog
</Button>
<Button
startIcon={<LibraryBooks />}
href="https://bitburner.readthedocs.io/en/latest/index.html"
target="_blank"
sx={{ gridArea: "docs" }}
>
Documentation
</Button>
<Button startIcon={<Chat />} href="https://discord.gg/TFc3hKD" target="_blank" sx={{ gridArea: "discord" }}>
Discord
</Button>
<Button
startIcon={<Reddit />}
href="https://www.reddit.com/r/bitburner"
target="_blank"
sx={{ gridArea: "reddit" }}
>
Reddit
</Button>
<Button
startIcon={<Public />}
href="https://plaza.dsolver.ca/games/bitburner"
target="_blank"
sx={{ gridArea: "plaza" }}
>
Incremental game plaza
</Button>
</Box>
{!location.href.startsWith("file://") && (
<Box sx={{ gridArea: "devs" }}>
<form
action="https://www.paypal.com/cgi-bin/webscr"
method="post"
target="_blank"
style={{ display: "block" }}
>
<Button sx={{ width: "100%", display: "flex", flexDirection: "column" }} type="submit">
danielyxie / BigD (Original Dev)
<input type="hidden" name="cmd" value="_s-xclick" />
<input
type="hidden"
name="encrypted"
value="-----BEGIN PKCS7-----MIIHRwYJKoZIhvcNAQcEoIIHODCCBzQCAQExggEwMIIBLAIBADCBlDCBjjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtQYXlQYWwgSW5jLjETMBEGA1UECxQKbGl2ZV9jZXJ0czERMA8GA1UEAxQIbGl2ZV9hcGkxHDAaBgkqhkiG9w0BCQEWDXJlQHBheXBhbC5jb20CAQAwDQYJKoZIhvcNAQEBBQAEgYA2Y2VGE75oWct89z//G2YEJKmzx0uDTXNrpje9ThxmUnBLFZCY+I11Pors7lGRvFqo5okwnu41CfYMPHDxpAgyYyQndMX9pWUX0gLfBMm2BaHwsNBCwt34WmpQqj7TGsQ+aw9NbmkxiJltGnOa+6/gy10mPZAA3HxiieLeCKkGgDELMAkGBSsOAwIaBQAwgcQGCSqGSIb3DQEHATAUBggqhkiG9w0DBwQI72F1YSzHUd2AgaDMekHU3AKT93Ey9wkB3486bV+ngFSD6VOHrPweH9QATsp+PMe9QM9vmq+s2bGtTbZaYrFqM3M97SnQ0l7IQ5yuOzdZhRdfysu5uJ8dnuHUzq4gLSzqMnZ6/3c+PoHB8AS1nYHUVL4U0+ogZsO1s97IAQyfck9SaoFlxVtqQhkb8752MkQJJvGu3ZQSQGcVC4hFDPk8prXqyq4BU/k/EliwoIIDhzCCA4MwggLsoAMCAQICAQAwDQYJKoZIhvcNAQEFBQAwgY4xCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEUMBIGA1UEChMLUGF5UGFsIEluYy4xEzARBgNVBAsUCmxpdmVfY2VydHMxETAPBgNVBAMUCGxpdmVfYXBpMRwwGgYJKoZIhvcNAQkBFg1yZUBwYXlwYWwuY29tMB4XDTA0MDIxMzEwMTMxNVoXDTM1MDIxMzEwMTMxNVowgY4xCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEUMBIGA1UEChMLUGF5UGFsIEluYy4xEzARBgNVBAsUCmxpdmVfY2VydHMxETAPBgNVBAMUCGxpdmVfYXBpMRwwGgYJKoZIhvcNAQkBFg1yZUBwYXlwYWwuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDBR07d/ETMS1ycjtkpkvjXZe9k+6CieLuLsPumsJ7QC1odNz3sJiCbs2wC0nLE0uLGaEtXynIgRqIddYCHx88pb5HTXv4SZeuv0Rqq4+axW9PLAAATU8w04qqjaSXgbGLP3NmohqM6bV9kZZwZLR/klDaQGo1u9uDb9lr4Yn+rBQIDAQABo4HuMIHrMB0GA1UdDgQWBBSWn3y7xm8XvVk/UtcKG+wQ1mSUazCBuwYDVR0jBIGzMIGwgBSWn3y7xm8XvVk/UtcKG+wQ1mSUa6GBlKSBkTCBjjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtQYXlQYWwgSW5jLjETMBEGA1UECxQKbGl2ZV9jZXJ0czERMA8GA1UEAxQIbGl2ZV9hcGkxHDAaBgkqhkiG9w0BCQEWDXJlQHBheXBhbC5jb22CAQAwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOBgQCBXzpWmoBa5e9fo6ujionW1hUhPkOBakTr3YCDjbYfvJEiv/2P+IobhOGJr85+XHhN0v4gUkEDI8r2/rNk1m0GA8HKddvTjyGw/XqXa+LSTlDYkqI8OwR8GEYj4efEtcRpRYBxV8KxAW93YDWzFGvruKnnLbDAF6VR5w/cCMn5hzGCAZowggGWAgEBMIGUMIGOMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDU1vdW50YWluIFZpZXcxFDASBgNVBAoTC1BheVBhbCBJbmMuMRMwEQYDVQQLFApsaXZlX2NlcnRzMREwDwYDVQQDFAhsaXZlX2FwaTEcMBoGCSqGSIb3DQEJARYNcmVAcGF5cGFsLmNvbQIBADAJBgUrDgMCGgUAoF0wGAYJKoZIhvcNAQkDMQsGCSqGSIb3DQEHATAcBgkqhkiG9w0BCQUxDxcNMTcwNzI1MDExODE2WjAjBgkqhkiG9w0BCQQxFgQUNo8efiZ7sk7nwKM/6B6Z7sU8hIIwDQYJKoZIhvcNAQEBBQAEgYB+JB4vZ/r48815/1HF/xK3+rOx7bPz3kAXmbhW/mkoF4OUbzqMeljvDIA9q/BDdlCLtxFOw9XlftTzv0eZCW/uCIiwu5wTzPIfPY1SI8WHe4cJbP2f2EYxIVs8D7OSirbW4yVa0+gACaLLj0rzIzNN8P/5PxgB03D+jwkcJABqng==-----END PKCS7-----"
/>
<input
type="image"
// src="https://www.paypalobjects.com/en_US/i/btn/btn_donateCC_LG.gif"
src="https://www.paypalobjects.com/digitalassets/c/website/marketing/apac/C2/logos-buttons/optimize/26_Yellow_PayPal_Pill_Button.png"
name="submit"
alt="PayPal - The safer, easier way to pay online!"
/>
</Button>
</form>
<Button
href="https://www.google.com/search?q=Where+to+donate+blood+near+me%3F"
target="_blank"
sx={{
width: "100%",
display: "flex",
flexDirection: "column",
}}
>
hydroflame (Current Maintainer)
<span style={{ display: "flex", alignItems: "center" }}>
<Bloodtype sx={{ mb: 0.5, mr: 1 }} />
Donate blood!
</span>
</Button>
</Box>
)}
</Box>
<FileDiagnosticModal open={diagnosticOpen} onClose={() => setDiagnosticOpen(false)} />
</Box>
);
};

@ -0,0 +1,38 @@
import { Slider, Tooltip, Typography, Box } from "@mui/material";
import React from "react";
interface IProps {
value: any;
callback: (event: any, newValue: number | number[]) => void;
step: number;
min: number;
max: number;
tooltip: React.ReactElement;
label: string;
marks?: boolean;
}
export const OptionsSlider = (props: IProps): React.ReactElement => {
return (
<Box>
<Tooltip title={<Typography>{props.tooltip}</Typography>}>
<Typography>{props.label}</Typography>
</Tooltip>
<Slider
value={props.value}
onChange={props.callback}
step={props.step}
min={props.min}
max={props.max}
valueLabelDisplay="auto"
sx={{
"& .MuiSlider-thumb": {
height: "12px",
width: "12px",
},
}}
marks={props.marks}
/>
</Box>
);
};

@ -6,6 +6,7 @@ export const GangConstants: {
CyclesPerTerritoryAndPowerUpdate: number;
AscensionMultiplierRatio: number;
Names: string[];
GangKarmaRequirement: number;
} = {
// Respect is divided by this to get rep gain
GangRespectToReputationRatio: 75,
@ -23,4 +24,5 @@ export const GangConstants: {
FactionNames.NiteSec,
FactionNames.TheBlackHand,
],
GangKarmaRequirement: -54000,
};

@ -42,6 +42,7 @@ export const HashUpgradesMetadata: IConstructorParams[] = [
costPerLevel: 50,
desc:
"Use hashes to increase the maximum amount of money on a single server by 2%. " +
"Note that a server's maximum money is soft capped above $10t. " +
"This effect persists until you install Augmentations (since servers " +
"are reset at that time).",
hasTargetServer: true,

@ -0,0 +1,25 @@
import { IPlayer } from "../../PersonObjects/IPlayer";
import { calculateSkill } from "../../PersonObjects/formulas/skill";
function calculateRawDiff(player: IPlayer, stats: number, startingDifficulty: number): number {
const difficulty = startingDifficulty - Math.pow(stats, 0.9) / 250 - player.intelligence / 1600;
if (difficulty < 0) return 0;
if (difficulty > 3) return 3;
return difficulty;
}
export function calculateDifficulty(player: IPlayer, startingSecurityLevel: number): number {
const totalStats = player.strength + player.defense + player.dexterity + player.agility + player.charisma;
return calculateRawDiff(player, totalStats, startingSecurityLevel);
}
export function calculateReward(player: IPlayer, startingSecurityLevel: number): number {
const xpMult = 10 * 60 * 15;
const total =
calculateSkill(player.strength_exp_mult * xpMult, player.strength_mult) +
calculateSkill(player.defense_exp_mult * xpMult, player.defense_mult) +
calculateSkill(player.agility_exp_mult * xpMult, player.agility_mult) +
calculateSkill(player.dexterity_exp_mult * xpMult, player.dexterity_mult) +
calculateSkill(player.charisma_exp_mult * xpMult, player.charisma_mult);
return calculateRawDiff(player, total, startingSecurityLevel);
}

@ -0,0 +1,53 @@
import { IPlayer } from "../../PersonObjects/IPlayer";
import { BitNodeMultipliers } from "../../BitNode/BitNodeMultipliers";
import { LocationsMetadata } from "../../Locations/data/LocationsMetadata";
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
import { Faction } from "../../Faction/Faction";
export function calculateSellInformationCashReward(
player: IPlayer,
reward: number,
maxLevel: number,
difficulty: number,
): number {
const levelBonus = maxLevel * Math.pow(1.01, maxLevel);
return (
Math.pow(reward + 1, 2) *
Math.pow(difficulty, 3) *
3e3 *
levelBonus *
(player.hasAugmentation(AugmentationNames.WKSharmonizer, true) ? 1.5 : 1) *
BitNodeMultipliers.InfiltrationMoney
);
}
export function calculateTradeInformationRepReward(
player: IPlayer,
reward: number,
maxLevel: number,
difficulty: number,
): number {
const levelBonus = maxLevel * Math.pow(1.01, maxLevel);
return (
Math.pow(reward + 1, 2) *
Math.pow(difficulty, 3) *
3e3 *
levelBonus *
(player.hasAugmentation(AugmentationNames.WKSharmonizer, true) ? 1.5 : 1) *
BitNodeMultipliers.InfiltrationMoney
);
}
export function calculateInfiltratorsRepReward(player: IPlayer, faction: Faction, difficulty: number): number {
const maxStartingSecurityLevel = LocationsMetadata.reduce((acc, data): number => {
const startingSecurityLevel = data.infiltrationData?.startingSecurityLevel || 0;
return acc > startingSecurityLevel ? acc : startingSecurityLevel;
}, 0);
const baseRepGain = (difficulty / maxStartingSecurityLevel) * 5000;
return (
baseRepGain * (player.hasAugmentation(AugmentationNames.WKSharmonizer, true) ? 2 : 1) * (1 + faction.favor / 100)
);
}

@ -8,6 +8,8 @@ import { interpolate } from "./Difficulty";
import { BlinkingCursor } from "./BlinkingCursor";
import Typography from "@mui/material/Typography";
import { KEY } from "../../utils/helpers/keyCodes";
import { Player } from "../../Player";
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
interface Difficulty {
[key: string]: number;
@ -34,6 +36,7 @@ export function BackwardGame(props: IMinigameProps): React.ReactElement {
const timer = difficulty.timer;
const [answer] = useState(makeAnswer(difficulty));
const [guess, setGuess] = useState("");
const hasAugment = Player.hasAugmentation(AugmentationNames.ChaosOfDionysus, true);
function press(this: Document, event: KeyboardEvent): void {
event.preventDefault();
@ -48,11 +51,13 @@ export function BackwardGame(props: IMinigameProps): React.ReactElement {
<Grid container spacing={3}>
<GameTimer millis={timer} onExpire={props.onFailure} />
<Grid item xs={12}>
<Typography variant="h4">Type it backward</Typography>
<Typography variant="h4">Type it{!hasAugment ? " backward" : ""}</Typography>
<KeyHandler onKeyDown={press} onFailure={props.onFailure} />
</Grid>
<Grid item xs={6}>
<Typography style={{ transform: "scaleX(-1)" }}>{answer}</Typography>
<Typography style={{ transform: hasAugment ? "none" : "scaleX(-1)", marginLeft: hasAugment ? "50%" : "none" }}>
{answer}
</Typography>
</Grid>
<Grid item xs={6}>
<Typography>

@ -7,6 +7,8 @@ import { random } from "../utils";
import { interpolate } from "./Difficulty";
import { BlinkingCursor } from "./BlinkingCursor";
import Typography from "@mui/material/Typography";
import { Player } from "../../Player";
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
import { KEY } from "../../utils/helpers/keyCodes";
interface Difficulty {
@ -31,9 +33,12 @@ const difficulties: {
function generateLeftSide(difficulty: Difficulty): string {
let str = "";
const options = [KEY.OPEN_BRACKET, KEY.LESS_THAN, KEY.OPEN_PARENTHESIS, KEY.OPEN_BRACE];
if (Player.hasAugmentation(AugmentationNames.WisdomOfAthena, true)) {
options.splice(0, 1);
}
const length = random(difficulty.min, difficulty.max);
for (let i = 0; i < length; i++) {
str += options[Math.floor(Math.random() * 4)];
str += options[Math.floor(Math.random() * options.length)];
}
return str;

@ -1,4 +1,4 @@
import React, { useState } from "react";
import React, { useEffect, useState } from "react";
import Grid from "@mui/material/Grid";
import { IMinigameProps } from "./IMinigameProps";
import { KeyHandler } from "./KeyHandler";
@ -6,6 +6,9 @@ import { GameTimer } from "./GameTimer";
import { interpolate } from "./Difficulty";
import Typography from "@mui/material/Typography";
import { KEY } from "../../utils/helpers/keyCodes";
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
import { Player } from "../../Player";
import { Settings } from "../../Settings/Settings";
import { downArrowSymbol, upArrowSymbol } from "../utils";
interface Difficulty {
@ -31,20 +34,54 @@ export function BribeGame(props: IMinigameProps): React.ReactElement {
interpolate(difficulties, props.difficulty, difficulty);
const timer = difficulty.timer;
const [choices] = useState(makeChoices(difficulty));
const [correctIndex, setCorrectIndex] = useState(0);
const [index, setIndex] = useState(0);
const currentChoice = choices[index];
useEffect(() => {
setCorrectIndex(choices.findIndex((choice) => positive.includes(choice)));
}, [choices]);
const defaultColor = Settings.theme.primary;
const disabledColor = Settings.theme.disabled;
let upColor = defaultColor;
let downColor = defaultColor;
let choiceColor = defaultColor;
const hasAugment = Player.hasAugmentation(AugmentationNames.BeautyOfAphrodite, true);
if (hasAugment) {
const upIndex = index + 1 >= choices.length ? 0 : index + 1;
let upDistance = correctIndex - upIndex;
if (upIndex > correctIndex) {
upDistance = choices.length - 1 - upIndex + correctIndex;
}
const downIndex = index - 1 < 0 ? choices.length - 1 : index - 1;
let downDistance = downIndex - correctIndex;
if (downIndex < correctIndex) {
downDistance = downIndex + choices.length - 1 - correctIndex;
}
const onCorrectIndex = correctIndex == index;
upColor = upDistance <= downDistance && !onCorrectIndex ? upColor : disabledColor;
downColor = upDistance >= downDistance && !onCorrectIndex ? downColor : disabledColor;
choiceColor = onCorrectIndex ? defaultColor : disabledColor;
}
function press(this: Document, event: KeyboardEvent): void {
event.preventDefault();
const k = event.key;
if (k === KEY.SPACE) {
if (positive.includes(choices[index])) props.onSuccess();
if (positive.includes(currentChoice)) props.onSuccess();
else props.onFailure();
return;
}
let newIndex = index;
if ([KEY.UP_ARROW, KEY.W, KEY.RIGHT_ARROW, KEY.D].map((key) => key as string).includes(k)) newIndex++;
if ([KEY.DOWN_ARROW, KEY.S, KEY.LEFT_ARROW, KEY.A].map((key) => key as string).includes(k)) newIndex--;
if ([KEY.UP_ARROW, KEY.W, KEY.RIGHT_ARROW, KEY.D].map((k) => k as string).includes(k)) newIndex++;
if ([KEY.DOWN_ARROW, KEY.S, KEY.LEFT_ARROW, KEY.A].map((k) => k as string).includes(k)) newIndex--;
while (newIndex < 0) newIndex += choices.length;
while (newIndex > choices.length - 1) newIndex -= choices.length;
setIndex(newIndex);
@ -58,13 +95,13 @@ export function BribeGame(props: IMinigameProps): React.ReactElement {
<KeyHandler onKeyDown={press} onFailure={props.onFailure} />
</Grid>
<Grid item xs={6}>
<Typography variant="h5" color="primary">
<Typography variant="h5" color={upColor}>
{upArrowSymbol}
</Typography>
<Typography variant="h5" color="primary">
{choices[index]}
<Typography variant="h5" color={choiceColor}>
{currentChoice}
</Typography>
<Typography variant="h5" color="primary">
<Typography variant="h5" color={downColor}>
{downArrowSymbol}
</Typography>
</Grid>

@ -3,9 +3,19 @@ import Grid from "@mui/material/Grid";
import { IMinigameProps } from "./IMinigameProps";
import { KeyHandler } from "./KeyHandler";
import { GameTimer } from "./GameTimer";
import { random, getArrow, rightArrowSymbol, leftArrowSymbol, upArrowSymbol, downArrowSymbol } from "../utils";
import {
random,
getArrow,
getInverseArrow,
leftArrowSymbol,
rightArrowSymbol,
upArrowSymbol,
downArrowSymbol,
} from "../utils";
import { interpolate } from "./Difficulty";
import Typography from "@mui/material/Typography";
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
import { Player } from "../../Player";
interface Difficulty {
[key: string]: number;
@ -32,10 +42,11 @@ export function CheatCodeGame(props: IMinigameProps): React.ReactElement {
const timer = difficulty.timer;
const [code] = useState(generateCode(difficulty));
const [index, setIndex] = useState(0);
const hasAugment = Player.hasAugmentation(AugmentationNames.TrickeryOfHermes, true);
function press(this: Document, event: KeyboardEvent): void {
event.preventDefault();
if (code[index] !== getArrow(event)) {
if (code[index] !== getArrow(event) && (!hasAugment || code[index] !== getInverseArrow(event))) {
props.onFailure();
return;
}

@ -7,6 +7,9 @@ import { interpolate } from "./Difficulty";
import { downArrowSymbol, getArrow, leftArrowSymbol, rightArrowSymbol, upArrowSymbol } from "../utils";
import Typography from "@mui/material/Typography";
import { KEY } from "../../utils/helpers/keyCodes";
import { Settings } from "../../Settings/Settings";
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
import { Player } from "../../Player";
interface Difficulty {
[key: string]: number;
@ -33,10 +36,11 @@ export function Cyberpunk2077Game(props: IMinigameProps): React.ReactElement {
interpolate(difficulties, props.difficulty, difficulty);
const timer = difficulty.timer;
const [grid] = useState(generatePuzzle(difficulty));
const [answer] = useState(generateAnswer(grid, difficulty));
const [index, setIndex] = useState(0);
const [answers] = useState(generateAnswers(grid, difficulty));
const [currentAnswerIndex, setCurrentAnswerIndex] = useState(0);
const [pos, setPos] = useState([0, 0]);
const hasAugment = Player.hasAugmentation(AugmentationNames.FloodOfPoseidon, true);
function press(this: Document, event: KeyboardEvent): void {
event.preventDefault();
const move = [0, 0];
@ -62,13 +66,13 @@ export function Cyberpunk2077Game(props: IMinigameProps): React.ReactElement {
if (event.key === KEY.SPACE) {
const selected = grid[pos[1]][pos[0]];
const expected = answer[index];
const expected = answers[currentAnswerIndex];
if (selected !== expected) {
props.onFailure();
return;
}
setIndex(index + 1);
if (answer.length === index + 1) props.onSuccess();
setCurrentAnswerIndex(currentAnswerIndex + 1);
if (answers.length === currentAnswerIndex + 1) props.onSuccess();
}
}
@ -78,17 +82,17 @@ export function Cyberpunk2077Game(props: IMinigameProps): React.ReactElement {
<GameTimer millis={timer} onExpire={props.onFailure} />
<Grid item xs={12}>
<Typography variant="h4">Match the symbols!</Typography>
<Typography variant="h5" color="primary">
<Typography variant="h5" color={Settings.theme.primary}>
Targets:{" "}
{answer.map((a, i) => {
if (i == index)
{answers.map((a, i) => {
if (i == currentAnswerIndex)
return (
<span key={`${i}`} style={{ fontSize: "1em", color: "blue" }}>
{a}&nbsp;
</span>
);
return (
<span key={`${i}`} style={{ fontSize: "1em" }}>
<span key={`${i}`} style={{ fontSize: "1em", color: Settings.theme.primary }}>
{a}&nbsp;
</span>
);
@ -99,14 +103,19 @@ export function Cyberpunk2077Game(props: IMinigameProps): React.ReactElement {
<div key={y}>
<Typography>
{line.map((cell, x) => {
if (x == pos[0] && y == pos[1])
const isCorrectAnswer = cell === answers[currentAnswerIndex];
if (x == pos[0] && y == pos[1]) {
return (
<span key={`${x}${y}`} style={{ fontSize: fontSize, color: "blue" }}>
{cell}&nbsp;
</span>
);
}
const optionColor = hasAugment && !isCorrectAnswer ? Settings.theme.disabled : Settings.theme.primary;
return (
<span key={`${x}${y}`} style={{ fontSize: fontSize }}>
<span key={`${x}${y}`} style={{ fontSize: fontSize, color: optionColor }}>
{cell}&nbsp;
</span>
);
@ -121,12 +130,12 @@ export function Cyberpunk2077Game(props: IMinigameProps): React.ReactElement {
);
}
function generateAnswer(grid: string[][], difficulty: Difficulty): string[] {
const answer = [];
function generateAnswers(grid: string[][], difficulty: Difficulty): string[] {
const answers = [];
for (let i = 0; i < Math.round(difficulty.symbols); i++) {
answer.push(grid[Math.floor(Math.random() * grid.length)][Math.floor(Math.random() * grid[0].length)]);
answers.push(grid[Math.floor(Math.random() * grid.length)][Math.floor(Math.random() * grid[0].length)]);
}
return answer;
return answers;
}
function randChar(): string {

@ -13,6 +13,7 @@ import { MinesweeperGame } from "./MinesweeperGame";
import { WireCuttingGame } from "./WireCuttingGame";
import { Victory } from "./Victory";
import Typography from "@mui/material/Typography";
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
interface IProps {
StartingDifficulty: number;
@ -91,7 +92,9 @@ export function Game(props: IProps): React.ReactElement {
pushResult(false);
// Kill the player immediately if they use automation, so
// it's clear they're not meant to
const damage = options?.automated ? player.hp : props.StartingDifficulty * 3;
const damage = options?.automated
? player.hp
: props.StartingDifficulty * 3 * (player.hasAugmentation(AugmentationNames.WKSharmonizer, true) ? 0.5 : 1);
if (player.takeDamage(damage)) {
router.toCity();
return;

@ -3,6 +3,8 @@ import React, { useState, useEffect } from "react";
import withStyles from "@mui/styles/withStyles";
import { Theme } from "@mui/material/styles";
import Grid from "@mui/material/Grid";
import { use } from "../../ui/Context";
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
const TimerProgress = withStyles((theme: Theme) => ({
root: {
@ -20,14 +22,16 @@ interface IProps {
}
export function GameTimer(props: IProps): React.ReactElement {
const player = use.Player();
const [v, setV] = useState(100);
const totalMillis = (player.hasAugmentation(AugmentationNames.WKSharmonizer, true) ? 1.3 : 1) * props.millis;
const tick = 200;
useEffect(() => {
const intervalId = setInterval(() => {
setV((old) => {
if (old <= 0) props.onExpire();
return old - (tick / props.millis) * 100;
return old - (tick / totalMillis) * 100;
});
}, tick);
return () => {

@ -1,48 +1,22 @@
import { IPlayer } from "../../PersonObjects/IPlayer";
import React, { useState } from "react";
import { Intro } from "./Intro";
import { Game } from "./Game";
import { Location } from "../../Locations/Location";
import { use } from "../../ui/Context";
import { calculateSkill } from "../../PersonObjects/formulas/skill";
import { calculateDifficulty, calculateReward } from "../formulas/game";
interface IProps {
location: Location;
}
function calcRawDiff(player: IPlayer, stats: number, startingDifficulty: number): number {
const difficulty = startingDifficulty - Math.pow(stats, 0.9) / 250 - player.intelligence / 1600;
if (difficulty < 0) return 0;
if (difficulty > 3) return 3;
return difficulty;
}
function calcDifficulty(player: IPlayer, startingDifficulty: number): number {
const totalStats = player.strength + player.defense + player.dexterity + player.agility + player.charisma;
return calcRawDiff(player, totalStats, startingDifficulty);
}
function calcReward(player: IPlayer, startingDifficulty: number): number {
const xpMult = 10 * 60 * 15;
const total =
calculateSkill(player.strength_exp_mult * xpMult, player.strength_mult) +
calculateSkill(player.defense_exp_mult * xpMult, player.defense_mult) +
calculateSkill(player.agility_exp_mult * xpMult, player.agility_mult) +
calculateSkill(player.dexterity_exp_mult * xpMult, player.dexterity_mult) +
calculateSkill(player.charisma_exp_mult * xpMult, player.charisma_mult);
return calcRawDiff(player, total, startingDifficulty);
}
export function InfiltrationRoot(props: IProps): React.ReactElement {
const player = use.Player();
const router = use.Router();
const [start, setStart] = useState(false);
if (props.location.infiltrationData === undefined) throw new Error("Trying to do infiltration on invalid location.");
const startingDifficulty = props.location.infiltrationData.startingSecurityLevel;
const difficulty = calcDifficulty(player, startingDifficulty);
const reward = calcReward(player, startingDifficulty);
console.log(`${difficulty} ${reward}`);
const startingSecurityLevel = props.location.infiltrationData.startingSecurityLevel;
const difficulty = calculateDifficulty(player, startingSecurityLevel);
const reward = calculateReward(player, startingSecurityLevel);
function cancel(): void {
router.toCity();
@ -62,7 +36,7 @@ export function InfiltrationRoot(props: IProps): React.ReactElement {
return (
<Game
StartingDifficulty={startingDifficulty}
StartingDifficulty={startingSecurityLevel}
Difficulty={difficulty}
Reward={reward}
MaxLevel={props.location.infiltrationData.maxClearanceLevel}

@ -7,6 +7,8 @@ import { interpolate } from "./Difficulty";
import { downArrowSymbol, getArrow, leftArrowSymbol, rightArrowSymbol, upArrowSymbol } from "../utils";
import Typography from "@mui/material/Typography";
import { KEY } from "../../utils/helpers/keyCodes";
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
import { Player } from "../../Player";
interface Difficulty {
[key: string]: number;
@ -36,7 +38,7 @@ export function MinesweeperGame(props: IMinigameProps): React.ReactElement {
const [answer, setAnswer] = useState(generateEmptyField(difficulty));
const [pos, setPos] = useState([0, 0]);
const [memoryPhase, setMemoryPhase] = useState(true);
const hasAugment = Player.hasAugmentation(AugmentationNames.HuntOfArtemis, true);
function press(this: Document, event: KeyboardEvent): void {
event.preventDefault();
if (memoryPhase) return;
@ -94,6 +96,7 @@ export function MinesweeperGame(props: IMinigameProps): React.ReactElement {
} else {
if (x == pos[0] && y == pos[1]) return <span key={x}>[X]&nbsp;</span>;
if (answer[y][x]) return <span key={x}>[.]&nbsp;</span>;
if (hasAugment && minefield[y][x]) return <span key={x}>[?]&nbsp;</span>;
return <span key={x}>[&nbsp;]&nbsp;</span>;
}
})}

@ -6,6 +6,8 @@ import { GameTimer } from "./GameTimer";
import { interpolate } from "./Difficulty";
import Typography from "@mui/material/Typography";
import { KEY } from "../../utils/helpers/keyCodes";
import { Player } from "../../Player";
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
interface Difficulty {
[key: string]: number;
@ -38,6 +40,10 @@ export function SlashGame(props: IMinigameProps): React.ReactElement {
props.onSuccess();
}
}
const hasAugment = Player.hasAugmentation(AugmentationNames.MightOfAres, true);
const phaseZeroTime = Math.random() * 3250 + 1500 - (250 + difficulty.window);
const phaseOneTime = 250;
const timeUntilAttacking = phaseZeroTime + phaseOneTime;
useEffect(() => {
let id = window.setTimeout(() => {
@ -45,8 +51,8 @@ export function SlashGame(props: IMinigameProps): React.ReactElement {
id = window.setTimeout(() => {
setPhase(2);
id = window.setTimeout(() => setPhase(0), difficulty.window);
}, 250);
}, Math.random() * 3250 + 1500 - (250 + difficulty.window));
}, phaseOneTime);
}, phaseZeroTime);
return () => {
clearInterval(id);
};
@ -57,6 +63,14 @@ export function SlashGame(props: IMinigameProps): React.ReactElement {
<GameTimer millis={5000} onExpire={props.onFailure} />
<Grid item xs={12}>
<Typography variant="h4">Slash when his guard is down!</Typography>
{hasAugment ? (
<>
<Typography variant="h4">Guard will drop in...</Typography>
<GameTimer millis={timeUntilAttacking} onExpire={props.onFailure} />
</>
) : (
<></>
)}
{phase === 0 && <Typography variant="h4">Guarding ...</Typography>}
{phase === 1 && <Typography variant="h4">Preparing?</Typography>}
{phase === 2 && <Typography variant="h4">ATTACKING!</Typography>}

@ -3,12 +3,19 @@ import React, { useState } from "react";
import Grid from "@mui/material/Grid";
import { Money } from "../../ui/React/Money";
import { Reputation } from "../../ui/React/Reputation";
import { BitNodeMultipliers } from "../../BitNode/BitNodeMultipliers";
import { use } from "../../ui/Context";
import Typography from "@mui/material/Typography";
import Button from "@mui/material/Button";
import MenuItem from "@mui/material/MenuItem";
import Select, { SelectChangeEvent } from "@mui/material/Select";
import { FactionNames } from "../../Faction/data/FactionNames";
import { formatNumber } from "../../utils/StringHelperFunctions";
import {
calculateInfiltratorsRepReward,
calculateSellInformationCashReward,
calculateTradeInformationRepReward,
} from "../formulas/victory";
import { inviteToFaction } from "../../Faction/FactionHelpers";
interface IProps {
StartingDifficulty: number;
@ -23,31 +30,25 @@ export function Victory(props: IProps): React.ReactElement {
const [faction, setFaction] = useState("none");
function quitInfiltration(): void {
handleInfiltrators();
router.toCity();
}
const levelBonus = props.MaxLevel * Math.pow(1.01, props.MaxLevel);
const soa = Factions[FactionNames.ShadowsOfAnarchy];
const repGain = calculateTradeInformationRepReward(player, props.Reward, props.MaxLevel, props.StartingDifficulty);
const moneyGain = calculateSellInformationCashReward(player, props.Reward, props.MaxLevel, props.StartingDifficulty);
const infiltrationRepGain = calculateInfiltratorsRepReward(player, soa, props.StartingDifficulty);
const repGain =
Math.pow(props.Reward + 1, 1.1) *
Math.pow(props.StartingDifficulty, 1.2) *
30 *
levelBonus *
BitNodeMultipliers.InfiltrationRep;
const moneyGain =
Math.pow(props.Reward + 1, 2) *
Math.pow(props.StartingDifficulty, 3) *
3e3 *
levelBonus *
BitNodeMultipliers.InfiltrationMoney;
const isMemberOfInfiltrators = player.factions.includes(FactionNames.ShadowsOfAnarchy);
function sell(): void {
handleInfiltrators();
player.gainMoney(moneyGain, "infiltration");
quitInfiltration();
}
function trade(): void {
handleInfiltrators();
if (faction === "none") return;
Factions[faction].playerReputation += repGain;
quitInfiltration();
@ -57,6 +58,13 @@ export function Victory(props: IProps): React.ReactElement {
setFaction(event.target.value);
}
function handleInfiltrators(): void {
inviteToFaction(Factions[FactionNames.ShadowsOfAnarchy]);
if (isMemberOfInfiltrators) {
soa.playerReputation += infiltrationRepGain;
}
}
return (
<>
<Grid container spacing={3}>
@ -65,7 +73,15 @@ export function Victory(props: IProps): React.ReactElement {
</Grid>
<Grid item xs={10}>
<Typography variant="h5" color="primary">
You can trade the confidential information you found for money or reputation.
You{" "}
{isMemberOfInfiltrators ? (
<>
have gained {formatNumber(infiltrationRepGain, 2)} rep for {FactionNames.ShadowsOfAnarchy} and{" "}
</>
) : (
<></>
)}
can trade the confidential information you found for money or reputation.
</Typography>
<Select value={faction} onChange={changeDropdown}>
<MenuItem key={"none"} value={"none"}>

@ -1,4 +1,4 @@
import React, { useState } from "react";
import React, { useEffect, useState } from "react";
import Grid from "@mui/material/Grid";
import Typography from "@mui/material/Typography";
import { IMinigameProps } from "./IMinigameProps";
@ -7,6 +7,9 @@ import { GameTimer } from "./GameTimer";
import { random } from "../utils";
import { interpolate } from "./Difficulty";
import { KEY } from "../../utils/helpers/keyCodes";
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
import { Player } from "../../Player";
import { Settings } from "../../Settings/Settings";
interface Difficulty {
[key: string]: number;
@ -61,23 +64,13 @@ export function WireCuttingGame(props: IMinigameProps): React.ReactElement {
const [wires] = useState(generateWires(difficulty));
const [cutWires, setCutWires] = useState(new Array(wires.length).fill(false));
const [questions] = useState(generateQuestion(wires, difficulty));
const hasAugment = Player.hasAugmentation(AugmentationNames.KnowledgeOfApollo, true);
function checkWire(wireNum: number): boolean {
return !questions.some((q) => q.shouldCut(wires[wireNum - 1], wireNum - 1));
}
function press(this: Document, event: KeyboardEvent): void {
event.preventDefault();
const wireNum = parseInt(event.key);
if (wireNum < 1 || wireNum > wires.length || isNaN(wireNum)) return;
setCutWires((old) => {
const next = [...old];
next[wireNum - 1] = true;
if (checkWire(wireNum)) {
props.onFailure();
return questions.some((q) => q.shouldCut(wires[wireNum - 1], wireNum - 1));
}
useEffect(() => {
// check if we won
const wiresToBeCut = [];
for (let j = 0; j < wires.length; j++) {
@ -87,9 +80,22 @@ export function WireCuttingGame(props: IMinigameProps): React.ReactElement {
}
wiresToBeCut.push(shouldBeCut);
}
if (wiresToBeCut.every((b, i) => b === next[i])) {
if (wiresToBeCut.every((b, i) => b === cutWires[i])) {
props.onSuccess();
}
}, [cutWires]);
function press(this: Document, event: KeyboardEvent): void {
event.preventDefault();
const wireNum = parseInt(event.key);
if (wireNum < 1 || wireNum > wires.length || isNaN(wireNum)) return;
setCutWires((old) => {
const next = [...old];
next[wireNum - 1] = true;
if (!checkWire(wireNum)) {
props.onFailure();
}
return next;
});
@ -104,18 +110,28 @@ export function WireCuttingGame(props: IMinigameProps): React.ReactElement {
<Typography key={i}>{question.toString()}</Typography>
))}
<Typography>
{new Array(wires.length).fill(0).map((_, i) => (
<span key={i}>&nbsp;{i + 1}&nbsp;&nbsp;&nbsp;&nbsp;</span>
))}
{new Array(wires.length).fill(0).map((_, i) => {
const isCorrectWire = checkWire(i + 1);
const color = hasAugment && !isCorrectWire ? Settings.theme.disabled : Settings.theme.primary;
return (
<span key={i} style={{ color: color }}>
&nbsp;{i + 1}&nbsp;&nbsp;&nbsp;&nbsp;
</span>
);
})}
</Typography>
{new Array(8).fill(0).map((_, i) => (
<div key={i}>
<Typography>
{wires.map((wire, j) => {
if ((i === 3 || i === 4) && cutWires[j])
if ((i === 3 || i === 4) && cutWires[j]) {
return <span key={j}>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>;
}
const isCorrectWire = checkWire(j + 1);
const wireColor =
hasAugment && !isCorrectWire ? Settings.theme.disabled : wire.colors[i % wire.colors.length];
return (
<span key={j} style={{ color: wire.colors[i % wire.colors.length] }}>
<span key={j} style={{ color: wireColor }}>
|{wire.tpe}|&nbsp;&nbsp;&nbsp;
</span>
);

@ -183,7 +183,7 @@ const Messages: Record<MessageFilenames, Message> = {
"like us. Because they can't hide from us. Because they can't fight shadows " +
"and ideas with bullets. <br><br>" +
"Join us, and people will fear you, too. <br><br>" +
"Find and install the backdoor on our server. Then, we will contact you again." +
"Find and install the backdoor on our server, avmnite-02h. Then, we will contact you again." +
`<br><br>-${FactionNames.NiteSec}`,
),

@ -70,6 +70,11 @@ export const RamCostConstants: IMap<number> = {
ScriptStanekPlace: 5,
ScriptStanekFragmentAt: 2,
ScriptStanekDeleteAt: 0.15,
ScriptInfiltrationCalculateDifficulty: 2.5,
ScriptInfiltrationCalculateRewards: 2.5,
ScriptInfiltrationGetLocations: 5,
ScriptInfiltrationGetInfiltrations: 15,
ScriptStanekAcceptGift: 2,
};
function SF4Cost(cost: number): (player: IPlayer) => number {
@ -244,6 +249,13 @@ const bladeburner: IMap<any> = {
getBonusTime: 0,
};
const infiltration: IMap<any> = {
calculateDifficulty: RamCostConstants.ScriptInfiltrationCalculateDifficulty,
calculateRewards: RamCostConstants.ScriptInfiltrationCalculateRewards,
calculateGetLocations: RamCostConstants.ScriptInfiltrationGetLocations,
calculateGetInfiltrations: RamCostConstants.ScriptInfiltrationGetInfiltrations,
};
// Coding Contract API
const codingcontract: IMap<any> = {
attempt: RamCostConstants.ScriptCodingContractBaseRamCost,
@ -284,6 +296,7 @@ const stanek: IMap<any> = {
placeFragment: RamCostConstants.ScriptStanekPlace,
getFragment: RamCostConstants.ScriptStanekFragmentAt,
removeFragment: RamCostConstants.ScriptStanekDeleteAt,
acceptGift: RamCostConstants.ScriptStanekAcceptGift,
};
// UI API
@ -312,6 +325,7 @@ export const RamCosts: IMap<any> = {
...singularity, // singularity is in namespace & toplevel
gang,
bladeburner,
infiltration,
codingcontract,
sleeve,
stanek,

@ -1,3 +1,4 @@
import $ from "jquery";
import { vsprintf, sprintf } from "sprintf-js";
import { getRamCost } from "./Netscript/RamCostGenerator";
@ -64,6 +65,7 @@ import { NetscriptSleeve } from "./NetscriptFunctions/Sleeve";
import { NetscriptExtra } from "./NetscriptFunctions/Extra";
import { NetscriptHacknet } from "./NetscriptFunctions/Hacknet";
import { NetscriptStanek } from "./NetscriptFunctions/Stanek";
import { NetscriptInfiltration } from "./NetscriptFunctions/Infiltration";
import { NetscriptUserInterface } from "./NetscriptFunctions/UserInterface";
import { NetscriptBladeburner } from "./NetscriptFunctions/Bladeburner";
import { NetscriptCodingContract } from "./NetscriptFunctions/CodingContract";
@ -80,6 +82,7 @@ import {
Gang as IGang,
Bladeburner as IBladeburner,
Stanek as IStanek,
Infiltration as IInfiltration,
RunningScript as IRunningScript,
RecentScript as IRecentScript,
SourceFileLvl,
@ -112,6 +115,7 @@ interface NS extends INS {
gang: IGang;
bladeburner: IBladeburner;
stanek: IStanek;
infiltration: IInfiltration;
}
export function NetscriptFunctions(workerScript: WorkerScript): NS {
@ -524,6 +528,8 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
const sleeve = NetscriptSleeve(Player, workerScript, helper);
const extra = NetscriptExtra(Player, workerScript, helper);
const hacknet = NetscriptHacknet(Player, workerScript, helper);
const infiltration = wrapAPI(helper, {}, workerScript, NetscriptInfiltration(Player), "infiltration")
.infiltration as unknown as IInfiltration;
const stanek = wrapAPI(helper, {}, workerScript, NetscriptStanek(Player, workerScript, helper), "stanek")
.stanek as unknown as IStanek;
const bladeburner = NetscriptBladeburner(Player, workerScript, helper);
@ -546,6 +552,7 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
sleeve: sleeve,
corporation: corporation,
stanek: stanek,
infiltration: infiltration,
ui: ui,
formulas: formulas,
stock: stockmarket,

@ -730,7 +730,22 @@ export function NetscriptCorporation(
const divisionName = helper.string("hireEmployee", "divisionName", _divisionName);
const cityName = helper.city("hireEmployee", "cityName", _cityName);
const office = getOffice(divisionName, cityName);
return office.hireRandomEmployee();
const employee = office.hireRandomEmployee();
if (employee === undefined) return undefined;
return {
name: employee.name,
mor: employee.mor,
hap: employee.hap,
ene: employee.ene,
int: employee.int,
cha: employee.cha,
exp: employee.exp,
cre: employee.cre,
eff: employee.eff,
sal: employee.sal,
loc: employee.loc,
pos: employee.pos,
};
},
upgradeOfficeSize: function (_divisionName: unknown, _cityName: unknown, _size: unknown): void {
checkAccess("upgradeOfficeSize", 8);

@ -38,6 +38,7 @@ import {
calculateAscensionPointsGain,
} from "../Gang/formulas/formulas";
import { favorToRep as calculateFavorToRep, repToFavor as calculateRepToFavor } from "../Faction/formulas/favor";
import { repFromDonation } from "../Faction/formulas/donation";
export function NetscriptFormulas(player: IPlayer, workerScript: WorkerScript, helper: INetscriptHelper): IFormulas {
const checkFormulasAccess = function (func: string): void {
@ -57,6 +58,11 @@ export function NetscriptFormulas(player: IPlayer, workerScript: WorkerScript, h
checkFormulasAccess("reputation.calculateRepToFavor");
return calculateRepToFavor(rep);
},
repFromDonation: function (_amount: unknown, player: any): number {
const amount = helper.number("repFromDonation", "amount", _amount);
checkFormulasAccess("reputation.repFromDonation");
return repFromDonation(amount, player);
},
},
skills: {
calculateSkill: function (_exp: unknown, _mult: unknown = 1): number {

@ -4,7 +4,7 @@ import { CityName } from "../Locations/data/CityNames";
import { getRamCost } from "../Netscript/RamCostGenerator";
import { WorkerScript } from "../Netscript/WorkerScript";
import { GraftableAugmentation } from "../PersonObjects/Grafting/GraftableAugmentation";
import { getGraftingAvailableAugs } from "../PersonObjects/Grafting/GraftingHelpers";
import { getGraftingAvailableAugs, calculateGraftingTimeWithBonus } from "../PersonObjects/Grafting/GraftingHelpers";
import { IPlayer } from "../PersonObjects/IPlayer";
import { Grafting as IGrafting } from "../ScriptEditor/NetscriptDefinitions";
import { Router } from "../ui/GameRoot";
@ -31,8 +31,8 @@ export function NetscriptGrafting(player: IPlayer, workerScript: WorkerScript, h
if (!getGraftingAvailableAugs(player).includes(augName) || !Augmentations.hasOwnProperty(augName)) {
throw helper.makeRuntimeErrorMsg("grafting.getAugmentationGraftPrice", `Invalid aug: ${augName}`);
}
const craftableAug = new GraftableAugmentation(Augmentations[augName]);
return craftableAug.cost;
const graftableAug = new GraftableAugmentation(Augmentations[augName]);
return graftableAug.cost;
},
getAugmentationGraftTime: (_augName: string): number => {
@ -42,8 +42,8 @@ export function NetscriptGrafting(player: IPlayer, workerScript: WorkerScript, h
if (!getGraftingAvailableAugs(player).includes(augName) || !Augmentations.hasOwnProperty(augName)) {
throw helper.makeRuntimeErrorMsg("grafting.getAugmentationGraftTime", `Invalid aug: ${augName}`);
}
const craftableAug = new GraftableAugmentation(Augmentations[augName]);
return craftableAug.time;
const graftableAug = new GraftableAugmentation(Augmentations[augName]);
return calculateGraftingTimeWithBonus(player, graftableAug);
},
getGraftableAugmentations: (): string[] => {

@ -0,0 +1,54 @@
import { IPlayer } from "../PersonObjects/IPlayer";
import { Infiltration as IInfiltration, InfiltrationLocation } from "../ScriptEditor/NetscriptDefinitions";
import { Location } from "../Locations/Location";
import { Locations } from "../Locations/Locations";
import { calculateDifficulty, calculateReward } from "../Infiltration/formulas/game";
import {
calculateInfiltratorsRepReward,
calculateSellInformationCashReward,
calculateTradeInformationRepReward,
} from "../Infiltration/formulas/victory";
import { FactionNames } from "../Faction/data/FactionNames";
import { Factions } from "../Faction/Factions";
import { InternalAPI, NetscriptContext } from "../Netscript/APIWrapper";
import { checkEnum } from "../utils/helpers/checkEnum";
import { LocationName } from "../Locations/data/LocationNames";
export function NetscriptInfiltration(player: IPlayer): InternalAPI<IInfiltration> {
const getLocationsWithInfiltrations = Object.values(Locations).filter(
(location: Location) => location.infiltrationData,
);
const calculateInfiltrationData = (ctx: NetscriptContext, locationName: string): InfiltrationLocation => {
if (!checkEnum(LocationName, locationName)) throw new Error(`Location '${locationName}' does not exists.`);
const location = Locations[locationName];
if (location === undefined) throw ctx.makeRuntimeErrorMsg(`Location '${location}' does not exists.`);
if (location.infiltrationData === undefined)
throw ctx.makeRuntimeErrorMsg(`Location '${location}' does not provide infiltrations.`);
const startingSecurityLevel = location.infiltrationData.startingSecurityLevel;
const difficulty = calculateDifficulty(player, startingSecurityLevel);
const reward = calculateReward(player, startingSecurityLevel);
const maxLevel = location.infiltrationData.maxClearanceLevel;
return {
location: location,
reward: {
tradeRep: calculateTradeInformationRepReward(player, reward, maxLevel, difficulty),
sellCash: calculateSellInformationCashReward(player, reward, maxLevel, difficulty),
SoARep: calculateInfiltratorsRepReward(player, Factions[FactionNames.ShadowsOfAnarchy], difficulty),
},
difficulty: difficulty,
};
};
return {
getPossibleLocations: () => (): string[] => {
return getLocationsWithInfiltrations.map((l) => l + "");
},
getInfiltration:
(ctx: NetscriptContext) =>
(_location: unknown): InfiltrationLocation => {
const location = ctx.helper.string("location", _location);
return calculateInfiltrationData(ctx, location);
},
};
}

@ -163,10 +163,7 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
const augs = getFactionAugmentationsFiltered(player, fac);
if (!augs.includes(augName)) {
workerScript.log(
"purchaseAugmentation",
() => `Faction '${facName}' does not have the '${augName}' augmentation.`,
);
_ctx.log(() => `Faction '${facName}' does not have the '${augName}' augmentation.`);
return false;
}
@ -174,25 +171,25 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
if (!isNeuroflux) {
for (let j = 0; j < player.queuedAugmentations.length; ++j) {
if (player.queuedAugmentations[j].name === aug.name) {
workerScript.log("purchaseAugmentation", () => `You already have the '${augName}' augmentation.`);
_ctx.log(() => `You already have the '${augName}' augmentation.`);
return false;
}
}
for (let j = 0; j < player.augmentations.length; ++j) {
if (player.augmentations[j].name === aug.name) {
workerScript.log("purchaseAugmentation", () => `You already have the '${augName}' augmentation.`);
_ctx.log(() => `You already have the '${augName}' augmentation.`);
return false;
}
}
}
if (fac.playerReputation < aug.baseRepRequirement) {
workerScript.log("purchaseAugmentation", () => `You do not have enough reputation with '${fac.name}'.`);
_ctx.log(() => `You do not have enough reputation with '${fac.name}'.`);
return false;
}
const res = purchaseAugmentation(aug, fac, true);
workerScript.log("purchaseAugmentation", () => res);
_ctx.log(() => res);
if (isString(res) && res.startsWith("You purchased")) {
player.gainIntelligenceExp(CONSTANTS.IntelligenceSingFnBaseExpGain * 10);
return true;
@ -205,7 +202,7 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
_ctx.helper.checkSingularityAccess();
const cbScript = _ctx.helper.string("cbScript", _cbScript);
workerScript.log("softReset", () => "Soft resetting. This will cause this script to be killed");
_ctx.log(() => "Soft resetting. This will cause this script to be killed");
setTimeout(() => {
installAugmentations(true);
runAfterReset(cbScript);
@ -221,14 +218,11 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
const cbScript = _ctx.helper.string("cbScript", _cbScript);
if (player.queuedAugmentations.length === 0) {
workerScript.log("installAugmentations", () => "You do not have any Augmentations to be installed.");
_ctx.log(() => "You do not have any Augmentations to be installed.");
return false;
}
player.gainIntelligenceExp(CONSTANTS.IntelligenceSingFnBaseExpGain * 10);
workerScript.log(
"installAugmentations",
() => "Installing Augmentations. This will cause this script to be killed",
);
_ctx.log(() => "Installing Augmentations. This will cause this script to be killed");
setTimeout(() => {
installAugmentations();
runAfterReset(cbScript);
@ -245,11 +239,11 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
const locationName = _ctx.helper.string("locationName", _locationName);
const location = Object.values(Locations).find((l) => l.name === locationName);
if (!location) {
workerScript.log("goToLocation", () => `No location named ${locationName}`);
_ctx.log(() => `No location named ${locationName}`);
return false;
}
if (player.city !== location.city) {
workerScript.log("goToLocation", () => `No location named ${locationName} in ${player.city}`);
_ctx.log(() => `No location named ${locationName} in ${player.city}`);
return false;
}
Router.toLocation(location);
@ -265,17 +259,14 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
const wasFocusing = player.focus;
if (player.isWorking) {
const txt = player.singularityStopWork();
workerScript.log("universityCourse", () => txt);
_ctx.log(() => txt);
}
let costMult, expMult;
switch (universityName.toLowerCase()) {
case LocationName.AevumSummitUniversity.toLowerCase():
if (player.city != CityName.Aevum) {
workerScript.log(
"universityCourse",
() => `You cannot study at 'Summit University' because you are not in '${CityName.Aevum}'.`,
);
_ctx.log(() => `You cannot study at 'Summit University' because you are not in '${CityName.Aevum}'.`);
return false;
}
player.gotoLocation(LocationName.AevumSummitUniversity);
@ -284,10 +275,7 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
break;
case LocationName.Sector12RothmanUniversity.toLowerCase():
if (player.city != CityName.Sector12) {
workerScript.log(
"universityCourse",
() => `You cannot study at 'Rothman University' because you are not in '${CityName.Sector12}'.`,
);
_ctx.log(() => `You cannot study at 'Rothman University' because you are not in '${CityName.Sector12}'.`);
return false;
}
player.location = LocationName.Sector12RothmanUniversity;
@ -296,8 +284,7 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
break;
case LocationName.VolhavenZBInstituteOfTechnology.toLowerCase():
if (player.city != CityName.Volhaven) {
workerScript.log(
"universityCourse",
_ctx.log(
() => `You cannot study at 'ZB Institute of Technology' because you are not in '${CityName.Volhaven}'.`,
);
return false;
@ -307,7 +294,7 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
expMult = 4;
break;
default:
workerScript.log("universityCourse", () => `Invalid university name: '${universityName}'.`);
_ctx.log(() => `Invalid university name: '${universityName}'.`);
return false;
}
@ -332,7 +319,7 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
task = CONSTANTS.ClassLeadership;
break;
default:
workerScript.log("universityCourse", () => `Invalid class name: ${className}.`);
_ctx.log(() => `Invalid class name: ${className}.`);
return false;
}
player.startClass(costMult, expMult, task);
@ -343,7 +330,7 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
player.stopFocusing();
Router.toTerminal();
}
workerScript.log("universityCourse", () => `Started ${task} at ${universityName}`);
_ctx.log(() => `Started ${task} at ${universityName}`);
return true;
},
@ -356,14 +343,13 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
const wasFocusing = player.focus;
if (player.isWorking) {
const txt = player.singularityStopWork();
workerScript.log("gymWorkout", () => txt);
_ctx.log(() => txt);
}
let costMult, expMult;
switch (gymName.toLowerCase()) {
case LocationName.AevumCrushFitnessGym.toLowerCase():
if (player.city != CityName.Aevum) {
workerScript.log(
"gymWorkout",
_ctx.log(
() =>
`You cannot workout at '${LocationName.AevumCrushFitnessGym}' because you are not in '${CityName.Aevum}'.`,
);
@ -375,8 +361,7 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
break;
case LocationName.AevumSnapFitnessGym.toLowerCase():
if (player.city != CityName.Aevum) {
workerScript.log(
"gymWorkout",
_ctx.log(
() =>
`You cannot workout at '${LocationName.AevumSnapFitnessGym}' because you are not in '${CityName.Aevum}'.`,
);
@ -388,8 +373,7 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
break;
case LocationName.Sector12IronGym.toLowerCase():
if (player.city != CityName.Sector12) {
workerScript.log(
"gymWorkout",
_ctx.log(
() =>
`You cannot workout at '${LocationName.Sector12IronGym}' because you are not in '${CityName.Sector12}'.`,
);
@ -401,8 +385,7 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
break;
case LocationName.Sector12PowerhouseGym.toLowerCase():
if (player.city != CityName.Sector12) {
workerScript.log(
"gymWorkout",
_ctx.log(
() =>
`You cannot workout at '${LocationName.Sector12PowerhouseGym}' because you are not in '${CityName.Sector12}'.`,
);
@ -414,8 +397,7 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
break;
case LocationName.VolhavenMilleniumFitnessGym.toLowerCase():
if (player.city != CityName.Volhaven) {
workerScript.log(
"gymWorkout",
_ctx.log(
() =>
`You cannot workout at '${LocationName.VolhavenMilleniumFitnessGym}' because you are not in '${CityName.Volhaven}'.`,
);
@ -426,7 +408,7 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
expMult = 4;
break;
default:
workerScript.log("gymWorkout", () => `Invalid gym name: ${gymName}. gymWorkout() failed`);
_ctx.log(() => `Invalid gym name: ${gymName}. gymWorkout() failed`);
return false;
}
@ -448,7 +430,7 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
player.startClass(costMult, expMult, CONSTANTS.ClassGymAgility);
break;
default:
workerScript.log("gymWorkout", () => `Invalid stat: ${stat}.`);
_ctx.log(() => `Invalid stat: ${stat}.`);
return false;
}
if (focus) {
@ -458,7 +440,7 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
player.stopFocusing();
Router.toTerminal();
}
workerScript.log("gymWorkout", () => `Started training ${stat} at ${gymName}`);
_ctx.log(() => `Started training ${stat} at ${gymName}`);
return true;
},
@ -475,12 +457,12 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
case CityName.Ishima:
case CityName.Volhaven:
if (player.money < CONSTANTS.TravelCost) {
workerScript.log("travelToCity", () => "Not enough money to travel.");
_ctx.log(() => "Not enough money to travel.");
return false;
}
player.loseMoney(CONSTANTS.TravelCost, "other");
player.city = cityName;
workerScript.log("travelToCity", () => `Traveled to ${cityName}`);
_ctx.log(() => `Traveled to ${cityName}`);
player.gainIntelligenceExp(CONSTANTS.IntelligenceSingFnBaseExpGain / 50000);
return true;
default:
@ -493,12 +475,12 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
_ctx.helper.checkSingularityAccess();
if (player.hasTorRouter()) {
workerScript.log("purchaseTor", () => "You already have a TOR router!");
_ctx.log(() => "You already have a TOR router!");
return true;
}
if (player.money < CONSTANTS.TorRouterCost) {
workerScript.log("purchaseTor", () => "You cannot afford to purchase a Tor router.");
_ctx.log(() => "You cannot afford to purchase a Tor router.");
return false;
}
player.loseMoney(CONSTANTS.TorRouterCost, "other");
@ -517,7 +499,7 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
player.getHomeComputer().serversOnNetwork.push(darkweb.hostname);
darkweb.serversOnNetwork.push(player.getHomeComputer().hostname);
player.gainIntelligenceExp(CONSTANTS.IntelligenceSingFnBaseExpGain / 500);
workerScript.log("purchaseTor", () => "You have purchased a Tor router!");
_ctx.log(() => "You have purchased a Tor router!");
return true;
},
purchaseProgram: (_ctx: NetscriptContext) =>
@ -526,26 +508,25 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
const programName = _ctx.helper.string("programName", _programName).toLowerCase();
if (!player.hasTorRouter()) {
workerScript.log("purchaseProgram", () => "You do not have the TOR router.");
_ctx.log(() => "You do not have the TOR router.");
return false;
}
const item = Object.values(DarkWebItems).find((i) => i.program.toLowerCase() === programName);
if (item == null) {
workerScript.log("purchaseProgram", () => `Invalid program name: '${programName}.`);
_ctx.log(() => `Invalid program name: '${programName}.`);
return false;
}
if (player.money < item.price) {
workerScript.log(
"purchaseProgram",
_ctx.log(
() => `Not enough money to purchase '${item.program}'. Need ${numeralWrapper.formatMoney(item.price)}`,
);
return false;
}
if (player.hasProgram(item.program)) {
workerScript.log("purchaseProgram", () => `You already have the '${item.program}' program`);
_ctx.log(() => `You already have the '${item.program}' program`);
return true;
}
@ -557,8 +538,7 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
}
player.loseMoney(item.price, "other");
workerScript.log(
"purchaseProgram",
_ctx.log(
() => `You have purchased the '${item.program}' program. The new program can be found on your home computer.`,
);
player.gainIntelligenceExp(CONSTANTS.IntelligenceSingFnBaseExpGain / 5000);
@ -629,7 +609,7 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
_ctx.helper.checkSingularityAccess();
const baseserver = player.getCurrentServer();
if (!(baseserver instanceof Server)) {
workerScript.log("installBackdoor", () => "cannot backdoor this kind of server");
_ctx.log(() => "cannot backdoor this kind of server");
return Promise.resolve();
}
const server = baseserver as Server;
@ -641,13 +621,12 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
throw _ctx.helper.makeRuntimeErrorMsg(canHack.msg || "");
}
workerScript.log(
"installBackdoor",
_ctx.log(
() => `Installing backdoor on '${server.hostname}' in ${convertTimeMsToTimeElapsedString(installTime, true)}`,
);
return netscriptDelay(installTime, workerScript).then(function () {
workerScript.log("installBackdoor", () => `Successfully installed backdoor on '${server.hostname}'`);
_ctx.log(() => `Successfully installed backdoor on '${server.hostname}'`);
server.backdoorInstalled = true;
@ -694,7 +673,7 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
getStats: (_ctx: NetscriptContext) =>
function (): PlayerSkills {
_ctx.helper.checkSingularityAccess();
workerScript.log("getStats", () => `getStats is deprecated, please use getplayer`);
_ctx.log(() => `getStats is deprecated, please use getplayer`);
return {
hacking: player.hacking,
@ -709,10 +688,7 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
getCharacterInformation: (_ctx: NetscriptContext) =>
function (): CharacterInfo {
_ctx.helper.checkSingularityAccess();
workerScript.log(
"getCharacterInformation",
() => `getCharacterInformation is deprecated, please use getplayer`,
);
_ctx.log(() => `getCharacterInformation is deprecated, please use getplayer`);
return {
bitnode: player.bitNodeN,
@ -763,7 +739,7 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
function (): void {
_ctx.helper.checkSingularityAccess();
if (player.isWorking || Router.page() === Page.Infiltration || Router.page() === Page.BitVerse) {
workerScript.log("hospitalize", () => "Cannot go to the hospital because the player is busy.");
_ctx.log(() => "Cannot go to the hospital because the player is busy.");
return;
}
player.hospitalize();
@ -782,7 +758,7 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
Router.toTerminal();
}
const txt = player.singularityStopWork();
workerScript.log("stopAction", () => txt);
_ctx.log(() => txt);
return true;
}
return false;
@ -794,16 +770,13 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
// Check if we're at max cores
const homeComputer = player.getHomeComputer();
if (homeComputer.cpuCores >= 8) {
workerScript.log("upgradeHomeCores", () => `Your home computer is at max cores.`);
_ctx.log(() => `Your home computer is at max cores.`);
return false;
}
const cost = player.getUpgradeHomeCoresCost();
if (player.money < cost) {
workerScript.log(
"upgradeHomeCores",
() => `You don't have enough money. Need ${numeralWrapper.formatMoney(cost)}`,
);
_ctx.log(() => `You don't have enough money. Need ${numeralWrapper.formatMoney(cost)}`);
return false;
}
@ -811,10 +784,7 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
player.loseMoney(cost, "servers");
player.gainIntelligenceExp(CONSTANTS.IntelligenceSingFnBaseExpGain * 2);
workerScript.log(
"upgradeHomeCores",
() => `Purchased an additional core for home computer! It now has ${homeComputer.cpuCores} cores.`,
);
_ctx.log(() => `Purchased an additional core for home computer! It now has ${homeComputer.cpuCores} cores.`);
return true;
},
getUpgradeHomeCoresCost: (_ctx: NetscriptContext) =>
@ -830,16 +800,13 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
// Check if we're at max RAM
const homeComputer = player.getHomeComputer();
if (homeComputer.maxRam >= CONSTANTS.HomeComputerMaxRam) {
workerScript.log("upgradeHomeRam", () => `Your home computer is at max RAM.`);
_ctx.log(() => `Your home computer is at max RAM.`);
return false;
}
const cost = player.getUpgradeHomeRamCost();
if (player.money < cost) {
workerScript.log(
"upgradeHomeRam",
() => `You don't have enough money. Need ${numeralWrapper.formatMoney(cost)}`,
);
_ctx.log(() => `You don't have enough money. Need ${numeralWrapper.formatMoney(cost)}`);
return false;
}
@ -847,8 +814,7 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
player.loseMoney(cost, "servers");
player.gainIntelligenceExp(CONSTANTS.IntelligenceSingFnBaseExpGain * 2);
workerScript.log(
"upgradeHomeRam",
_ctx.log(
() =>
`Purchased additional RAM for home computer! It now has ${numeralWrapper.formatRAM(
homeComputer.maxRam,
@ -875,13 +841,13 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
// Make sure its a valid company
if (companyName == null || companyName === "" || !(Companies[companyName] instanceof Company)) {
workerScript.log("workForCompany", () => `Invalid company: '${companyName}'`);
_ctx.log(() => `Invalid company: '${companyName}'`);
return false;
}
// Make sure player is actually employed at the comapny
if (!Object.keys(player.jobs).includes(companyName)) {
workerScript.log("workForCompany", () => `You do not have a job at '${companyName}'`);
_ctx.log(() => `You do not have a job at '${companyName}'`);
return false;
}
@ -889,14 +855,14 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
const companyPositionName = player.jobs[companyName];
const companyPosition = CompanyPositions[companyPositionName];
if (companyPositionName === "" || !(companyPosition instanceof CompanyPosition)) {
workerScript.log("workForCompany", () => "You do not have a job");
_ctx.log(() => "You do not have a job");
return false;
}
const wasFocused = player.focus;
if (player.isWorking) {
const txt = player.singularityStopWork();
workerScript.log("workForCompany", () => txt);
_ctx.log(() => txt);
}
if (companyPosition.isPartTimeJob()) {
@ -912,10 +878,7 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
player.stopFocusing();
Router.toTerminal();
}
workerScript.log(
"workForCompany",
() => `Began working at '${player.companyName}' as a '${companyPositionName}'`,
);
_ctx.log(() => `Began working at '${player.companyName}' as a '${companyPositionName}'`);
return true;
},
applyToCompany: (_ctx: NetscriptContext) =>
@ -968,25 +931,19 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
res = player.applyForPartTimeWaiterJob(true);
break;
default:
workerScript.log("applyToCompany", () => `Invalid job: '${field}'.`);
_ctx.log(() => `Invalid job: '${field}'.`);
return false;
}
// TODO https://github.com/danielyxie/bitburner/issues/1378
// The player object's applyForJob function can return string with special error messages
// if (isString(res)) {
// workerScript.log("applyToCompany",()=> res);
// _ctx.log("applyToCompany",()=> res);
// return false;
// }
if (res) {
workerScript.log(
"applyToCompany",
() => `You were offered a new job at '${companyName}' as a '${player.jobs[companyName]}'`,
);
_ctx.log(() => `You were offered a new job at '${companyName}' as a '${player.jobs[companyName]}'`);
} else {
workerScript.log(
"applyToCompany",
() => `You failed to get a new job/promotion at '${companyName}' in the '${field}' field.`,
);
_ctx.log(() => `You failed to get a new job/promotion at '${companyName}' in the '${field}' field.`);
}
return res;
},
@ -1024,7 +981,7 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
getFaction(_ctx, facName);
if (!player.factionInvitations.includes(facName)) {
workerScript.log("joinFaction", () => `You have not been invited by faction '${facName}'`);
_ctx.log(() => `You have not been invited by faction '${facName}'`);
return false;
}
const fac = Factions[facName];
@ -1038,7 +995,7 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
}
}
player.gainIntelligenceExp(CONSTANTS.IntelligenceSingFnBaseExpGain * 5);
workerScript.log("joinFaction", () => `Joined the '${facName}' faction.`);
_ctx.log(() => `Joined the '${facName}' faction.`);
return true;
},
workForFaction: (_ctx: NetscriptContext) =>
@ -1051,22 +1008,19 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
// if the player is in a gang and the target faction is any of the gang faction, fail
if (player.inGang() && faction.name === player.getGangFaction().name) {
workerScript.log(
"workForFaction",
() => `You can't work for '${facName}' because youre managing a gang for it`,
);
_ctx.log(() => `You can't work for '${facName}' because youre managing a gang for it`);
return false;
}
if (!player.factions.includes(facName)) {
workerScript.log("workForFaction", () => `You are not a member of '${facName}'`);
_ctx.log(() => `You are not a member of '${facName}'`);
return false;
}
const wasFocusing = player.focus;
if (player.isWorking) {
const txt = player.singularityStopWork();
workerScript.log("workForFaction", () => txt);
_ctx.log(() => txt);
}
switch (type.toLowerCase()) {
@ -1074,10 +1028,7 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
case "hacking contracts":
case "hackingcontracts":
if (!FactionInfos[faction.name].offerHackingWork) {
workerScript.log(
"workForFaction",
() => `Faction '${faction.name}' do not need help with hacking contracts.`,
);
_ctx.log(() => `Faction '${faction.name}' do not need help with hacking contracts.`);
return false;
}
player.startFactionHackWork(faction);
@ -1088,16 +1039,13 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
player.stopFocusing();
Router.toTerminal();
}
workerScript.log("workForFaction", () => `Started carrying out hacking contracts for '${faction.name}'`);
_ctx.log(() => `Started carrying out hacking contracts for '${faction.name}'`);
return true;
case "field":
case "fieldwork":
case "field work":
if (!FactionInfos[faction.name].offerFieldWork) {
workerScript.log(
"workForFaction",
() => `Faction '${faction.name}' do not need help with field missions.`,
);
_ctx.log(() => `Faction '${faction.name}' do not need help with field missions.`);
return false;
}
player.startFactionFieldWork(faction);
@ -1108,16 +1056,13 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
player.stopFocusing();
Router.toTerminal();
}
workerScript.log("workForFaction", () => `Started carrying out field missions for '${faction.name}'`);
_ctx.log(() => `Started carrying out field missions for '${faction.name}'`);
return true;
case "security":
case "securitywork":
case "security work":
if (!FactionInfos[faction.name].offerSecurityWork) {
workerScript.log(
"workForFaction",
() => `Faction '${faction.name}' do not need help with security work.`,
);
_ctx.log(() => `Faction '${faction.name}' do not need help with security work.`);
return false;
}
player.startFactionSecurityWork(faction);
@ -1128,10 +1073,10 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
player.stopFocusing();
Router.toTerminal();
}
workerScript.log("workForFaction", () => `Started carrying out security work for '${faction.name}'`);
_ctx.log(() => `Started carrying out security work for '${faction.name}'`);
return true;
default:
workerScript.log("workForFaction", () => `Invalid work type: '${type}`);
_ctx.log(() => `Invalid work type: '${type}`);
return false;
}
return true;
@ -1164,38 +1109,28 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
const amt = _ctx.helper.number("amt", _amt);
const faction = getFaction(_ctx, facName);
if (!player.factions.includes(faction.name)) {
workerScript.log("donateToFaction", () => `You can't donate to '${facName}' because you aren't a member`);
_ctx.log(() => `You can't donate to '${facName}' because you aren't a member`);
return false;
}
if (player.inGang() && faction.name === player.getGangFaction().name) {
workerScript.log(
"donateToFaction",
() => `You can't donate to '${facName}' because youre managing a gang for it`,
);
_ctx.log(() => `You can't donate to '${facName}' because youre managing a gang for it`);
return false;
}
if (faction.name === FactionNames.ChurchOfTheMachineGod || faction.name === FactionNames.Bladeburners) {
workerScript.log(
"donateToFaction",
() => `You can't donate to '${facName}' because they do not accept donations`,
);
_ctx.log(() => `You can't donate to '${facName}' because they do not accept donations`);
return false;
}
if (typeof amt !== "number" || amt <= 0 || isNaN(amt)) {
workerScript.log("donateToFaction", () => `Invalid donation amount: '${amt}'.`);
_ctx.log(() => `Invalid donation amount: '${amt}'.`);
return false;
}
if (player.money < amt) {
workerScript.log(
"donateToFaction",
() => `You do not have enough money to donate ${numeralWrapper.formatMoney(amt)} to '${facName}'`,
);
_ctx.log(() => `You do not have enough money to donate ${numeralWrapper.formatMoney(amt)} to '${facName}'`);
return false;
}
const repNeededToDonate = Math.floor(CONSTANTS.BaseFavorToDonate * BitNodeMultipliers.RepToDonateToFaction);
if (faction.favor < repNeededToDonate) {
workerScript.log(
"donateToFaction",
_ctx.log(
() =>
`You do not have enough favor to donate to this faction. Have ${faction.favor}, need ${repNeededToDonate}`,
);
@ -1204,8 +1139,7 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
const repGain = (amt / CONSTANTS.DonateMoneyToRepDivisor) * player.faction_rep_mult;
faction.playerReputation += repGain;
player.loseMoney(amt, "other");
workerScript.log(
"donateToFaction",
_ctx.log(
() =>
`${numeralWrapper.formatMoney(amt)} donated to '${facName}' for ${numeralWrapper.formatReputation(
repGain,
@ -1222,32 +1156,29 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
const wasFocusing = player.focus;
if (player.isWorking) {
const txt = player.singularityStopWork();
workerScript.log("createProgram", () => txt);
_ctx.log(() => txt);
}
const p = Object.values(Programs).find((p) => p.name.toLowerCase() === programName);
if (p == null) {
workerScript.log("createProgram", () => `The specified program does not exist: '${programName}`);
_ctx.log(() => `The specified program does not exist: '${programName}`);
return false;
}
if (player.hasProgram(p.name)) {
workerScript.log("createProgram", () => `You already have the '${p.name}' program`);
_ctx.log(() => `You already have the '${p.name}' program`);
return false;
}
const create = p.create;
if (create === null) {
workerScript.log("createProgram", () => `You cannot create the '${p.name}' program`);
_ctx.log(() => `You cannot create the '${p.name}' program`);
return false;
}
if (!create.req(player)) {
workerScript.log(
"createProgram",
() => `Hacking level is too low to create '${p.name}' (level ${create.level} req)`,
);
_ctx.log(() => `Hacking level is too low to create '${p.name}' (level ${create.level} req)`);
return false;
}
@ -1259,7 +1190,7 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
player.stopFocusing();
Router.toTerminal();
}
workerScript.log("createProgram", () => `Began creating program: '${programName}'`);
_ctx.log(() => `Began creating program: '${programName}'`);
return true;
},
commitCrime: (_ctx: NetscriptContext) =>
@ -1269,7 +1200,7 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
if (player.isWorking) {
const txt = player.singularityStopWork();
workerScript.log("commitCrime", () => txt);
_ctx.log(() => txt);
}
// Set Location to slums
@ -1280,7 +1211,7 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
// couldn't find crime
throw _ctx.helper.makeRuntimeErrorMsg(`Invalid crime: '${crimeRoughName}'`);
}
workerScript.log("commitCrime", () => `Attempting to commit ${crime.name}...`);
_ctx.log(() => `Attempting to commit ${crime.name}...`);
return crime.commit(Router, player, 1, workerScript);
},
getCrimeChance: (_ctx: NetscriptContext) =>
@ -1313,7 +1244,7 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
// If we don't have Tor, log it and return [] (empty list)
if (!player.hasTorRouter()) {
workerScript.log("getDarkwebPrograms", () => "You do not have the TOR router.");
_ctx.log(() => "You do not have the TOR router.");
return [];
}
return Object.values(DarkWebItems).map((p) => p.program);
@ -1325,7 +1256,7 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
// If we don't have Tor, log it and return -1
if (!player.hasTorRouter()) {
workerScript.log("getDarkwebProgramCost", () => "You do not have the TOR router.");
_ctx.log(() => "You do not have the TOR router.");
// returning -1 rather than throwing an error to be consistent with purchaseProgram
// which returns false if tor has
return -1;
@ -1345,7 +1276,7 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
}
if (player.hasProgram(item.program)) {
workerScript.log("getDarkwebProgramCost", () => `You already have the '${item.program}' program`);
_ctx.log(() => `You already have the '${item.program}' program`);
return 0;
}
return item.price;

@ -13,6 +13,10 @@ import {
} from "../ScriptEditor/NetscriptDefinitions";
import { AugmentationNames } from "../Augmentation/data/AugmentationNames";
import { NetscriptContext, InternalAPI } from "../Netscript/APIWrapper";
import { applyAugmentation } from "../Augmentation/AugmentationHelpers";
import { FactionNames } from "../Faction/data/FactionNames";
import { joinFaction } from "../Faction/FactionHelpers";
import { Factions } from "../Faction/Factions";
export function NetscriptStanek(
player: IPlayer,
@ -109,5 +113,29 @@ export function NetscriptStanek(
checkStanekAPIAccess("removeFragment");
return staneksGift.delete(rootX, rootY);
},
acceptGift: (_ctx: NetscriptContext) =>
function (): boolean {
//Check if the player is eligible to join the church
if (
player.canAccessCotMG() &&
player.augmentations.filter((a) => a.name !== AugmentationNames.NeuroFluxGovernor).length == 0 &&
player.queuedAugmentations.filter((a) => a.name !== AugmentationNames.NeuroFluxGovernor).length == 0
) {
//Attempt to join CotMG
joinFaction(Factions[FactionNames.ChurchOfTheMachineGod]);
//Attempt to install the first Stanek aug
if (
!player.hasAugmentation(AugmentationNames.StaneksGift1) &&
!player.queuedAugmentations.some((a) => a.name === AugmentationNames.StaneksGift1)
) {
applyAugmentation({ name: AugmentationNames.StaneksGift1, level: 1 });
}
}
//Return true iff the player is in CotMG and has the first Stanek aug installed
return (
Factions[FactionNames.ChurchOfTheMachineGod].isMember &&
player.hasAugmentation(AugmentationNames.StaneksGift1)
);
},
};
}

@ -358,7 +358,6 @@ export function NetscriptStockMarket(player: IPlayer, workerScript: WorkerScript
},
purchase4SMarketData: function (): boolean {
updateRam("purchase4SMarketData");
checkTixApiAccess("purchase4SMarketData");
if (player.has4SData) {
workerScript.log("stock.purchase4SMarketData", () => "Already purchased 4S Market Data.");

@ -1,5 +1,6 @@
import { Augmentations } from "../../Augmentation/Augmentations";
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
import { GraftableAugmentation } from "./GraftableAugmentation";
import { IPlayer } from "../IPlayer";
export const getGraftingAvailableAugs = (player: IPlayer): string[] => {
@ -13,3 +14,12 @@ export const getGraftingAvailableAugs = (player: IPlayer): string[] => {
return augs.filter((augmentation: string) => !player.hasAugmentation(augmentation));
};
export const graftingIntBonus = (player: IPlayer): number => {
return 1 + (player.getIntelligenceBonus(3) - 1) / 3;
};
export const calculateGraftingTimeWithBonus = (player: IPlayer, aug: GraftableAugmentation): number => {
const baseTime = aug.time;
return baseTime / graftingIntBonus(player);
};

@ -1,6 +1,6 @@
import { Construction, CheckBox, CheckBoxOutlineBlank } from "@mui/icons-material";
import { Box, Button, Container, List, ListItemButton, Paper, Typography } from "@mui/material";
import React, { useState } from "react";
import React, { useState, useEffect } from "react";
import { Augmentation } from "../../../Augmentation/Augmentation";
import { Augmentations } from "../../../Augmentation/Augmentations";
import { AugmentationNames } from "../../../Augmentation/data/AugmentationNames";
@ -15,7 +15,7 @@ import { ConfirmationModal } from "../../../ui/React/ConfirmationModal";
import { Money } from "../../../ui/React/Money";
import { convertTimeMsToTimeElapsedString, formatNumber } from "../../../utils/StringHelperFunctions";
import { IPlayer } from "../../IPlayer";
import { getGraftingAvailableAugs } from "../GraftingHelpers";
import { getGraftingAvailableAugs, calculateGraftingTimeWithBonus } from "../GraftingHelpers";
import { GraftableAugmentation } from "../GraftableAugmentation";
const GraftableAugmentations: IMap<GraftableAugmentation> = {};
@ -63,6 +63,16 @@ export const GraftingRoot = (): React.ReactElement => {
const [selectedAug, setSelectedAug] = useState(getGraftingAvailableAugs(player)[0]);
const [graftOpen, setGraftOpen] = useState(false);
const setRerender = useState(false)[1];
function rerender(): void {
setRerender((old) => !old);
}
useEffect(() => {
const id = setInterval(rerender, 200);
return () => clearInterval(id);
}, []);
return (
<Container disableGutters maxWidth="lg" sx={{ mx: 0 }}>
<Button onClick={() => router.toLocation(Locations[LocationName.NewTokyoVitaLife])}>Back</Button>
@ -132,7 +142,7 @@ export const GraftingRoot = (): React.ReactElement => {
<Typography color={Settings.theme.info}>
<b>Time to Graft:</b>{" "}
{convertTimeMsToTimeElapsedString(
GraftableAugmentations[selectedAug].time / (1 + (player.getIntelligenceBonus(3) - 1) / 3),
calculateGraftingTimeWithBonus(player, GraftableAugmentations[selectedAug]),
)}
{/* Use formula so the displayed creation time is accurate to player bonus */}
</Typography>

@ -202,6 +202,7 @@ export interface IPlayer extends IPerson {
hasProgram(program: string): boolean;
inBladeburner(): boolean;
inGang(): boolean;
isAwareOfGang(): boolean;
isQualified(company: Company, position: CompanyPosition): boolean;
loseMoney(money: number, source: string): void;
process(router: IRouter, numCycles?: number): void;

@ -78,6 +78,13 @@ export abstract class Person implements IPerson {
bladeburner_analysis_mult;
bladeburner_success_chance_mult;
infiltration_base_rep_increase = 0;
infiltration_rep_mult = 1;
infiltration_trade_mult = 1;
infiltration_sell_mult = 1;
infiltration_timer_mult = 1;
infiltration_damage_reduction_mult = 1;
/**
* Augmentations
*/
@ -279,6 +286,24 @@ export abstract class Person implements IPerson {
this.crime_success_mult = 1;
this.work_money_mult = 1;
this.hacknet_node_money_mult = 1;
this.hacknet_node_purchase_cost_mult = 1;
this.hacknet_node_ram_cost_mult = 1;
this.hacknet_node_core_cost_mult = 1;
this.hacknet_node_level_cost_mult = 1;
this.bladeburner_max_stamina_mult = 1;
this.bladeburner_stamina_gain_mult = 1;
this.bladeburner_analysis_mult = 1;
this.bladeburner_success_chance_mult = 1;
this.infiltration_base_rep_increase = 0;
this.infiltration_rep_mult = 1;
this.infiltration_trade_mult = 1;
this.infiltration_sell_mult = 1;
this.infiltration_timer_mult = 1;
this.infiltration_damage_reduction_mult = 1;
}
/**

@ -220,6 +220,7 @@ export class PlayerObject implements IPlayer {
hasProgram: (program: string) => boolean;
inBladeburner: () => boolean;
inGang: () => boolean;
isAwareOfGang: () => boolean;
isQualified: (company: Company, position: CompanyPosition) => boolean;
loseMoney: (money: number, source: string) => void;
reapplyAllAugmentations: (resetMultipliers?: boolean) => void;
@ -607,6 +608,7 @@ export class PlayerObject implements IPlayer {
this.hasCorporation = corporationMethods.hasCorporation;
this.startCorporation = corporationMethods.startCorporation;
this.canAccessGang = gangMethods.canAccessGang;
this.isAwareOfGang = gangMethods.isAwareOfGang;
this.getGangFaction = gangMethods.getGangFaction;
this.getGangName = gangMethods.getGangName;
this.hasGangWith = gangMethods.hasGangWith;

@ -7,7 +7,7 @@ import { Augmentation } from "../../Augmentation/Augmentation";
import { calculateEntropy } from "../Grafting/EntropyAccumulation";
export function hasAugmentation(this: IPlayer, aug: string | Augmentation, includeQueued = false): boolean {
export function hasAugmentation(this: IPlayer, aug: string | Augmentation, ignoreQueued = false): boolean {
const augName: string = aug instanceof Augmentation ? aug.name : aug;
for (const owned of this.augmentations) {
@ -16,7 +16,7 @@ export function hasAugmentation(this: IPlayer, aug: string | Augmentation, inclu
}
}
if (!includeQueued) {
if (!ignoreQueued) {
for (const owned of this.queuedAugmentations) {
if (owned.name === augName) {
return true;

@ -2,9 +2,9 @@ import { Factions } from "../../Faction/Factions";
import { Faction } from "../../Faction/Faction";
import { Gang } from "../../Gang/Gang";
import { IPlayer } from "../IPlayer";
import { GangConstants } from "../../Gang/data/Constants"
// Amount of negative karma needed to manage a gang in BitNodes other than 2
const GangKarmaRequirement = -54000;
export function canAccessGang(this: IPlayer): boolean {
if (this.bitNodeN === 2) {
@ -14,7 +14,11 @@ export function canAccessGang(this: IPlayer): boolean {
return false;
}
return this.karma <= GangKarmaRequirement;
return this.karma <= GangConstants.GangKarmaRequirement;
}
export function isAwareOfGang(this: IPlayer): boolean {
return this.bitNodeN === 2 || this.sourceFileLvl(2) >= 1;
}
export function getGangFaction(this: IPlayer): Faction {

@ -68,6 +68,7 @@ import { FactionNames } from "../../Faction/data/FactionNames";
import { ITaskTracker } from "../ITaskTracker";
import { IPerson } from "../IPerson";
import { use } from "../../ui/Context";
import { graftingIntBonus } from "../Grafting/GraftingHelpers";
export function init(this: IPlayer): void {
/* Initialize Player's home computer */
@ -1364,7 +1365,7 @@ export function craftAugmentationWork(this: IPlayer, numCycles: number): boolean
focusBonus = this.focus ? 1 : CONSTANTS.BaseFocusBonus;
}
let skillMult = 1 + (this.getIntelligenceBonus(3) - 1) / 3;
let skillMult = graftingIntBonus(this);
skillMult *= focusBonus;
this.timeWorked += CONSTANTS._idleSpeed * numCycles;
@ -1884,13 +1885,12 @@ export function getNextCompanyPosition(
export function quitJob(this: IPlayer, company: string): void {
if (this.isWorking == true && this.workType.includes("Working for Company") && this.companyName == company) {
this.isWorking = false;
this.companyName = "";
}
if (this.companyName === company) {
this.companyName = "";
this.finishWork(true);
}
delete this.jobs[company];
if (this.companyName === company) {
this.companyName = this.hasJob() ? Object.keys(this.jobs)[0] : "";
}
}
/**
@ -2181,7 +2181,7 @@ export function checkForFactionInvitations(this: IPlayer): Faction[] {
!daedalusFac.isBanned &&
!daedalusFac.isMember &&
!daedalusFac.alreadyInvited &&
numAugmentations >= Math.round(30 * BitNodeMultipliers.DaedalusAugsRequirement) &&
numAugmentations >= BitNodeMultipliers.DaedalusAugsRequirement &&
this.money >= 100000000000 &&
(this.hacking >= 2500 ||
(this.strength >= 1500 && this.defense >= 1500 && this.dexterity >= 1500 && this.agility >= 1500))

@ -586,6 +586,14 @@ export class Sleeve extends Person {
break;
}
// If the player has a gang with the faction the sleeve is working
// for, we need to reset the sleeve's task
if (p.gang) {
if (fac.name === p.gang.facName) {
this.resetTaskStatus();
}
}
fac.playerReputation += this.getRepGain(p) * cyclesUsed;
break;
}

@ -65,7 +65,7 @@ function possibleJobs(player: IPlayer, sleeve: Sleeve): string[] {
function possibleFactions(player: IPlayer, sleeve: Sleeve): string[] {
// Array of all factions that other sleeves are working for
const forbiddenFactions = [FactionNames.Bladeburners as string];
const forbiddenFactions = [FactionNames.Bladeburners as string, FactionNames.ShadowsOfAnarchy as string];
if (player.gang) {
forbiddenFactions.push(player.gang.facName);
}
@ -140,6 +140,8 @@ const tasks: {
first: factions,
second: (s1: string) => {
const faction = Factions[s1];
if (!faction) return ["------"];
const facInfo = faction.getInfo();
const options: string[] = [];
if (facInfo.offerHackingWork) {
@ -306,7 +308,7 @@ export function TaskSelector(props: IProps): React.ReactElement {
const detailsF = tasks[n];
if (detailsF === undefined) throw new Error(`No function for task '${s0}'`);
const details = detailsF(props.player, props.sleeve);
const details2 = details.second(details.first[0]);
const details2 = details.second(details.first[0]) ?? ["------"];
setS2(details2[0]);
setS1(details.first[0]);
setS0(n);

@ -307,7 +307,7 @@ export const programsMetadata: IProgramCreationParams[] = [
name: "fl1ght.exe",
create: null,
run: (router: IRouter, terminal: ITerminal, player: IPlayer): void => {
const numAugReq = Math.round(BitNodeMultipliers.DaedalusAugsRequirement * 30);
const numAugReq = BitNodeMultipliers.DaedalusAugsRequirement;
const fulfilled = player.augmentations.length >= numAugReq && player.money > 1e11 && player.hacking >= 2500;
if (!fulfilled) {
terminal.print(`Augmentations: ${player.augmentations.length} / ${numAugReq}`);

@ -23,6 +23,9 @@ import { LocationName } from "./Locations/data/LocationNames";
import { SxProps } from "@mui/system";
import { PlayerObject } from "./PersonObjects/Player/PlayerObject";
import { pushGameSaved } from "./Electron";
import { defaultMonacoTheme } from "./ScriptEditor/ui/themes";
import { FactionNames } from "./Faction/data/FactionNames";
import { Faction } from "./Faction/Faction";
/* SaveObject.js
* Defines the object used to save/load games
@ -394,8 +397,11 @@ function evaluateVersionCompatibility(ver: string | number): void {
delete anyPlayer.resleeves;
}
}
if (ver < 14) {
delete (Settings as any).EditorTheme;
if (ver < 15) {
(Settings as any).EditorTheme = { ...defaultMonacoTheme };
}
if (ver < 16) {
Factions[FactionNames.ShadowsOfAnarchy] = new Faction(FactionNames.ShadowsOfAnarchy);
}
}
}

@ -215,6 +215,9 @@ async function parseOnlyRamCalculate(
} else if (ref in workerScript.env.vars.stanek) {
func = workerScript.env.vars.stanek[ref];
refDetail = `stanek.${ref}`;
} else if (ref in workerScript.env.vars.infiltration) {
func = workerScript.env.vars.infiltration[ref];
refDetail = `infiltration.${ref}`;
} else if (ref in workerScript.env.vars.gang) {
func = workerScript.env.vars.gang[ref];
refDetail = `gang.${ref}`;

@ -2198,7 +2198,7 @@ export interface Singularity {
* RAM cost: 5 GB * 16/4/1
*
*
* This function will automatically install your Augmentations, resetting the game as usual.
* This function will automatically install your Augmentations, resetting the game as usual. If you do not own uninstalled Augmentations then the game will not reset.
*
* @param cbScript - This is a script that will automatically be run after Augmentations are installed (after the reset). This script will be run with no arguments and 1 thread. It must be located on your home computer.
*/
@ -3873,6 +3873,13 @@ interface ReputationFormulas {
* @returns The calculated faction favor.
*/
calculateRepToFavor(rep: number): number;
/**
* Calculate how much rep would be gained.
* @param amount - Amount of money donated
* @param player - Player info from {@link NS.getPlayer | getPlayer}
*/
repFromDonation(amount: number, player: Player): number;
}
/**
@ -4254,6 +4261,51 @@ interface Stanek {
* @returns The fragment at [rootX, rootY], if any.
*/
removeFragment(rootX: number, rootY: number): boolean;
/**
* Accept Stanek's Gift by joining the Church of the Machine God
* @remarks
* RAM cost: 2 GB
*
* @returns true if the player is a member of the church and has the gift installed,
* false otherwise.
*/
acceptGift(): boolean;
}
export interface InfiltrationReward {
tradeRep: number;
sellCash: number;
SoARep: number;
}
export interface InfiltrationLocation {
location: any;
reward: InfiltrationReward;
difficulty: number;
}
/**
* Infiltration API.
* @public
*/
interface Infiltration {
/**
* Get all locations that can be infiltrated.
* @remarks
* RAM cost: 5 GB
*
* @returns all locations that can be infiltrated.
*/
getPossibleLocations(): string[];
/**
* Get all infiltrations with difficulty, location and rewards.
* @remarks
* RAM cost: 15 GB
*
* @returns Infiltration data for given location.
*/
getInfiltration(location: string): InfiltrationLocation;
}
/**
@ -4405,6 +4457,11 @@ export interface NS {
* RAM cost: 0 GB
*/
readonly stanek: Stanek;
/**
* Namespace for infiltration functions.
* RAM cost: 0 GB
*/
readonly infiltration: Infiltration;
/**
* Namespace for corporation functions.
* RAM cost: 0 GB

@ -30,6 +30,10 @@ export function cd(
terminal.error("Invalid path. Failed to change directories");
return;
}
if (terminal.cwd().length > 1 && dir === "..") {
terminal.setcwd(evaledDir);
return;
}
const server = player.getCurrentServer();
if (!containsFiles(server, evaledDir)) {

@ -1,3 +1,5 @@
import $ from "jquery";
import { ITerminal } from "../ITerminal";
import { IRouter } from "../../ui/Router";
import { IPlayer } from "../../PersonObjects/IPlayer";

@ -3,8 +3,9 @@ import Typography from "@mui/material/Typography";
import { Theme } from "@mui/material/styles";
import makeStyles from "@mui/styles/makeStyles";
import createStyles from "@mui/styles/createStyles";
import Paper from "@mui/material/Paper";
import Popper from "@mui/material/Popper";
import TextField from "@mui/material/TextField";
import Tooltip from "@mui/material/Tooltip";
import { KEY } from "../../utils/helpers/keyCodes";
import { ITerminal } from "../ITerminal";
@ -376,22 +377,6 @@ export function TerminalInput({ terminal, router, player }: IProps): React.React
return (
<>
<Tooltip
title={
possibilities.length > 0 ? (
<>
<Typography classes={{ root: classes.preformatted }} color={"primary"} paragraph={false}>
Possible autocomplete candidate:
</Typography>
<Typography classes={{ root: classes.preformatted }} color={"primary"} paragraph={false}>
{possibilities.join(" ")}
</Typography>
</>
) : (
""
)
}
>
<TextField
fullWidth
color={terminal.action === null ? "primary" : "secondary"}
@ -412,10 +397,20 @@ export function TerminalInput({ terminal, router, player }: IProps): React.React
</Typography>
),
spellCheck: false,
onBlur: () => setPossibilities([]),
onKeyDown: onKeyDown,
}}
></TextField>
</Tooltip>
<Popper open={possibilities.length > 0} anchorEl={terminalInput.current} placement={"top-start"}>
<Paper sx={{ m: 1, p: 2 }}>
<Typography classes={{ root: classes.preformatted }} color={"primary"} paragraph={false}>
Possible autocomplete candidates:
</Typography>
<Typography classes={{ root: classes.preformatted }} color={"primary"} paragraph={false}>
{possibilities.join(" ")}
</Typography>
</Paper>
</Popper>
</>
);
}

@ -1,6 +1,7 @@
import { getRandomInt } from "../utils/helpers/getRandomInt";
import { MinHeap } from "../utils/Heap";
import { comprGenChar, comprLZGenerate, comprLZEncode, comprLZDecode } from "../utils/CompressionContracts";
import { HammingEncode, HammingDecode } from "../utils/HammingCodeTools";
/* tslint:disable:completed-docs no-magic-numbers arrow-return-shorthand */
@ -1306,4 +1307,305 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [
return parseInt(ans, 10) === HammingDecode(data);
},
},
{
name: "Proper 2-Coloring of a Graph",
difficulty: 7,
numTries: 5,
desc: (data: [number, [number, number][]]): string => {
return [
`You are given the following data, representing a graph:\n`,
`${JSON.stringify(data)}\n`,
`Note that "graph", as used here, refers to the field of graph theory, and has`,
`no relation to statistics or plotting.`,
`The first element of the data represents the number of vertices in the graph.`,
`Each vertex is a unique number between 0 and ${data[0] - 1}.`,
`The next element of the data represents the edges of the graph.`,
`Two vertices u,v in a graph are said to be adjacent if there exists an edge [u,v].`,
`Note that an edge [u,v] is the same as an edge [v,u], as order does not matter.`,
`You must construct a 2-coloring of the graph, meaning that you have to assign each`,
`vertex in the graph a "color", either 0 or 1, such that no two adjacent vertices have`,
`the same color. Submit your answer in the form of an array, where element i`,
`represents the color of vertex i. If it is impossible to construct a 2-coloring of`,
`the given graph, instead submit an empty array.\n\n`,
`Examples:\n\n`,
`Input: [4, [[0, 2], [0, 3], [1, 2], [1, 3]]]\n`,
`Output: [0, 0, 1, 1]\n\n`,
`Input: [3, [[0, 1], [0, 2], [1, 2]]]\n`,
`Output: []`,
].join(" ");
},
gen: (): [number, [number, number][]] => {
//Generate two partite sets
const n = Math.floor(Math.random() * 5) + 3;
const m = Math.floor(Math.random() * 5) + 3;
//50% chance of spawning any given valid edge in the bipartite graph
const edges: [number, number][] = [];
for (let i = 0; i < n; i++) {
for (let j = 0; j < m; j++) {
if (Math.random() > 0.5) {
edges.push([i, n + j]);
}
}
}
//Add an edge at random with no regard to partite sets
let a = Math.floor(Math.random() * (n + m));
let b = Math.floor(Math.random() * (n + m));
if (a > b) [a, b] = [b, a]; //Enforce lower numbers come first
if (a != b && !edges.includes([a, b])) {
edges.push([a, b]);
}
//Randomize array in-place using Durstenfeld shuffle algorithm.
function shuffle(array: any[]): void {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]];
}
}
//Replace instances of the original vertex names in-place
const vertexShuffler = Array.from(Array(n + m).keys());
shuffle(vertexShuffler);
for (let i = 0; i < edges.length; i++) {
edges[i] = [vertexShuffler[edges[i][0]], vertexShuffler[edges[i][1]]];
if (edges[i][0] > edges[i][1]) {
//Enforce lower numbers come first
[edges[i][0], edges[i][1]] = [edges[i][1], edges[i][0]];
}
}
//Shuffle the order of the edges themselves, as well
shuffle(edges);
return [n + m, edges];
},
solver: (data: [number, [number, number][]], ans: string): boolean => {
//Case where the player believes there is no solution
if (ans == "[]") {
//Helper function to get neighbourhood of a vertex
function neighbourhood(vertex: number): number[] {
const adjLeft = data[1].filter(([a, _]) => a == vertex).map(([_, b]) => b);
const adjRight = data[1].filter(([_, b]) => b == vertex).map(([a, _]) => a);
return adjLeft.concat(adjRight);
}
//Verify that there is no solution by attempting to create a proper 2-coloring.
const coloring: (number | undefined)[] = Array(data[0]).fill(undefined);
while (coloring.some((val) => val === undefined)) {
//Color a vertex in the graph
const initialVertex: number = coloring.findIndex((val) => val === undefined);
coloring[initialVertex] = 0;
const frontier: number[] = [initialVertex];
//Propogate the coloring throughout the component containing v greedily
while (frontier.length > 0) {
const v: number = frontier.pop() || 0;
const neighbors: number[] = neighbourhood(v);
//For each vertex u adjacent to v
for (const id in neighbors) {
const u: number = neighbors[id];
//Set the color of u to the opposite of v's color if it is new,
//then add u to the frontier to continue the algorithm.
if (coloring[u] === undefined) {
if (coloring[v] === 0) coloring[u] = 1;
else coloring[u] = 0;
frontier.push(u);
}
//Assert u,v do not have the same color
else if (coloring[u] === coloring[v]) {
//If u,v do have the same color, no proper 2-coloring exists, meaning
//the player was correct to say there is no proper 2-coloring of the graph.
return true;
}
}
}
}
//If this code is reached, there exists a proper 2-coloring of the input
//graph, and thus the player was incorrect in submitting no answer.
return false;
}
//Sanitize player input
const sanitizedPlayerAns: string = removeBracketsFromArrayString(ans);
const sanitizedPlayerAnsArr: string[] = sanitizedPlayerAns.split(",");
const coloring: number[] = sanitizedPlayerAnsArr.map((val) => parseInt(val));
//Solution provided case
if (coloring.length == data[0]) {
const edges = data[1];
const validColors = [0, 1];
//Check that the provided solution is a proper 2-coloring
return edges.every(([a, b]) => {
const aColor = coloring[a];
const bColor = coloring[b];
return (
validColors.includes(aColor) && //Enforce the first endpoint is color 0 or 1
validColors.includes(bColor) && //Enforce the second endpoint is color 0 or 1
aColor != bColor //Enforce the endpoints are different colors
);
});
}
//Return false if the coloring is the wrong size
else return false;
},
},
{
name: "Compression I: RLE Compression",
difficulty: 2,
numTries: 10,
desc: (plaintext: string): string => {
return [
"Run-length encoding (RLE) is a data compression technique which encodes data as a series of runs of",
"a repeated single character. Runs are encoded as a length, followed by the character itself. Lengths",
"are encoded as a single ASCII digit; runs of 10 characters or more are encoded by splitting them",
"into multiple runs.\n\n",
"You are given the following input string:\n",
`&nbsp; &nbsp; ${plaintext}\n`,
"Encode it using run-length encoding with the minimum possible output length.\n\n",
"Examples:\n",
"&nbsp; &nbsp; aaaaabccc &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;-> &nbsp;5a1b3c\n",
"&nbsp; &nbsp; aAaAaA &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; -> &nbsp;1a1A1a1A1a1A\n",
"&nbsp; &nbsp; 111112333 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;-> &nbsp;511233\n",
"&nbsp; &nbsp; zzzzzzzzzzzzzzzzzzz &nbsp;-> &nbsp;9z9z1z &nbsp;(or 9z8z2z, etc.)\n",
].join(" ");
},
gen: (): string => {
const length = 50 + Math.floor(25 * (Math.random() + Math.random()));
let plain = "";
while (plain.length < length) {
const r = Math.random();
let n = 1;
if (r < 0.3) {
n = 1;
} else if (r < 0.6) {
n = 2;
} else if (r < 0.9) {
n = Math.floor(10 * Math.random());
} else {
n = 10 + Math.floor(5 * Math.random());
}
const c = comprGenChar();
plain += c.repeat(n);
}
return plain.substring(0, length);
},
solver: (plain: string, ans: string): boolean => {
if (ans.length % 2 !== 0) {
return false;
}
let ans_plain = "";
for (let i = 0; i + 1 < ans.length; i += 2) {
const length = ans.charCodeAt(i) - 0x30;
if (length < 0 || length > 9) {
return false;
}
ans_plain += ans[i + 1].repeat(length);
}
if (ans_plain !== plain) {
return false;
}
let length = 0;
for (let i = 0; i < plain.length; ) {
let run_length = 1;
while (i + run_length < plain.length && plain[i + run_length] === plain[i]) {
++run_length;
}
i += run_length;
while (run_length > 0) {
run_length -= 9;
length += 2;
}
}
return ans.length === length;
},
},
{
name: "Compression II: LZ Decompression",
difficulty: 4,
numTries: 10,
desc: (compressed: string): string => {
return [
"Lempel-Ziv (LZ) compression is a data compression technique which encodes data using references to",
"earlier parts of the data. In this variant of LZ, data is encoded in two types of chunk. Each chunk",
"begins with a length L, encoded as a single ASCII digit from 1 - 9, followed by the chunk data,",
"which is either:\n\n",
"1. Exactly L characters, which are to be copied directly into the uncompressed data.\n",
"2. A reference to an earlier part of the uncompressed data. To do this, the length is followed",
"by a second ASCII digit X: each of the L output characters is a copy of the character X",
"places before it in the uncompressed data.\n\n",
"For both chunk types, a length of 0 instead means the chunk ends immediately, and the next character",
"is the start of a new chunk. The two chunk types alternate, starting with type 1, and the final",
"chunk may be of either type.\n\n",
"You are given the following LZ-encoded string:\n",
`&nbsp; &nbsp; ${compressed}\n`,
"Decode it and output the original string.\n\n",
"Example: decoding '5aaabc340533bca' chunk-by-chunk\n",
"&nbsp; &nbsp; 5aaabc &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; -> &nbsp;aaabc\n",
"&nbsp; &nbsp; 5aaabc34 &nbsp; &nbsp; &nbsp; &nbsp; -> &nbsp;aaabcaab\n",
"&nbsp; &nbsp; 5aaabc340 &nbsp; &nbsp; &nbsp; &nbsp;-> &nbsp;aaabcaab\n",
"&nbsp; &nbsp; 5aaabc34053 &nbsp; &nbsp; &nbsp;-> &nbsp;aaabcaabaabaa\n",
"&nbsp; &nbsp; 5aaabc340533bca &nbsp;-> &nbsp;aaabcaabaabaabca",
].join(" ");
},
gen: (): string => {
return comprLZEncode(comprLZGenerate());
},
solver: (compr: string, ans: string): boolean => {
return ans === comprLZDecode(compr);
},
},
{
name: "Compression III: LZ Compression",
difficulty: 10,
numTries: 10,
desc: (plaintext: string): string => {
return [
"Lempel-Ziv (LZ) compression is a data compression technique which encodes data using references to",
"earlier parts of the data. In this variant of LZ, data is encoded in two types of chunk. Each chunk",
"begins with a length L, encoded as a single ASCII digit from 1 - 9, followed by the chunk data,",
"which is either:\n\n",
"1. Exactly L characters, which are to be copied directly into the uncompressed data.\n",
"2. A reference to an earlier part of the uncompressed data. To do this, the length is followed",
"by a second ASCII digit X: each of the L output characters is a copy of the character X",
"places before it in the uncompressed data.\n\n",
"For both chunk types, a length of 0 instead means the chunk ends immediately, and the next character",
"is the start of a new chunk. The two chunk types alternate, starting with type 1, and the final",
"chunk may be of either type.\n\n",
"You are given the following input string:\n",
`&nbsp; &nbsp; ${plaintext}\n`,
"Encode it using Lempel-Ziv encoding with the minimum possible output length.\n\n",
"Examples (some have other possible encodings of minimal length):\n",
"&nbsp; &nbsp; abracadabra &nbsp; &nbsp;-> &nbsp;7abracad47\n",
"&nbsp; &nbsp; mississippi &nbsp; &nbsp;-> &nbsp;4miss433ppi\n",
"&nbsp; &nbsp; aAAaAAaAaAA &nbsp; &nbsp;-> &nbsp;3aAA53035\n",
"&nbsp; &nbsp; 2718281828 &nbsp; &nbsp; -> &nbsp;627182844\n",
"&nbsp; &nbsp; abcdefghijk &nbsp; &nbsp;-> &nbsp;9abcdefghi02jk\n",
"&nbsp; &nbsp; aaaaaaaaaaa &nbsp; &nbsp;-> &nbsp;1a911a\n",
"&nbsp; &nbsp; aaaaaaaaaaaa &nbsp; -> &nbsp;1a912aa\n",
"&nbsp; &nbsp; aaaaaaaaaaaaa &nbsp;-> &nbsp;1a91031",
].join(" ");
},
gen: (): string => {
return comprLZGenerate();
},
solver: (plain: string, ans: string): boolean => {
return comprLZDecode(ans) === plain && ans.length === comprLZEncode(plain).length;
},
},
];

@ -82,13 +82,12 @@ function CurrentBitNode(): React.ReactElement {
const player = use.Player();
if (player.sourceFiles.length > 0) {
const index = "BitNode" + player.bitNodeN;
const currentSourceFile = player.sourceFiles.find((sourceFile) => sourceFile.n == player.bitNodeN);
const lvl = currentSourceFile ? currentSourceFile.lvl : 0;
const lvl = player.sourceFileLvl(player.bitNodeN) + 1;
return (
<Box>
<Paper sx={{ p: 1 }}>
<Typography variant="h5">
BitNode {player.bitNodeN}: {BitNodes[index].name} (Level {lvl + 1})
BitNode {player.bitNodeN}: {BitNodes[index].name} (Level {lvl})
</Typography>
<Typography sx={{ whiteSpace: "pre-wrap", overflowWrap: "break-word" }}>{BitNodes[index].info}</Typography>
</Paper>

@ -44,7 +44,7 @@ import { CorporationRoot } from "../Corporation/ui/CorporationRoot";
import { InfiltrationRoot } from "../Infiltration/ui/InfiltrationRoot";
import { GraftingRoot } from "../PersonObjects/Grafting/ui/GraftingRoot";
import { WorkInProgressRoot } from "./WorkInProgressRoot";
import { GameOptionsRoot } from "./React/GameOptionsRoot";
import { GameOptionsRoot } from "../GameOptions/ui/GameOptionsRoot";
import { SleeveRoot } from "../PersonObjects/Sleeve/ui/SleeveRoot";
import { HacknetRoot } from "../Hacknet/ui/HacknetRoot";
import { GenericLocation } from "../Locations/ui/GenericLocation";

@ -1,667 +0,0 @@
import React, { useState, useRef } from "react";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { Theme } from "@mui/material/styles";
import makeStyles from "@mui/styles/makeStyles";
import createStyles from "@mui/styles/createStyles";
import Typography from "@mui/material/Typography";
import Slider from "@mui/material/Slider";
import Grid from "@mui/material/Grid";
import Select, { SelectChangeEvent } from "@mui/material/Select";
import MenuItem from "@mui/material/MenuItem";
import Button from "@mui/material/Button";
import Box from "@mui/material/Box";
import List from "@mui/material/List";
import ListItem from "@mui/material/ListItem";
import Link from "@mui/material/Link";
import Tooltip from "@mui/material/Tooltip";
import TextField from "@mui/material/TextField";
import DownloadIcon from "@mui/icons-material/Download";
import UploadIcon from "@mui/icons-material/Upload";
import SaveIcon from "@mui/icons-material/Save";
import PaletteIcon from "@mui/icons-material/Palette";
import { FileDiagnosticModal } from "../../Diagnostic/FileDiagnosticModal";
import { ConfirmationModal } from "./ConfirmationModal";
import { SnackbarEvents, ToastVariant } from "./Snackbar";
import { Settings } from "../../Settings/Settings";
import { DeleteGameButton } from "./DeleteGameButton";
import { SoftResetButton } from "./SoftResetButton";
import { IRouter } from "../Router";
import { ThemeEditorButton } from "../../Themes/ui/ThemeEditorButton";
import { StyleEditorButton } from "../../Themes/ui/StyleEditorButton";
import { formatTime } from "../../utils/helpers/formatTime";
import { OptionSwitch } from "./OptionSwitch";
import { ImportData, saveObject } from "../../SaveObject";
import { convertTimeMsToTimeElapsedString } from "../../utils/StringHelperFunctions";
const useStyles = makeStyles((theme: Theme) =>
createStyles({
root: {
width: 50,
padding: theme.spacing(2),
userSelect: "none",
},
}),
);
interface IProps {
player: IPlayer;
router: IRouter;
save: () => void;
export: () => void;
forceKill: () => void;
softReset: () => void;
}
export function GameOptionsRoot(props: IProps): React.ReactElement {
const classes = useStyles();
const importInput = useRef<HTMLInputElement>(null);
const [execTime, setExecTime] = useState(Settings.CodeInstructionRunTime);
const [recentScriptsSize, setRecentScriptsSize] = useState(Settings.MaxRecentScriptsCapacity);
const [logSize, setLogSize] = useState(Settings.MaxLogCapacity);
const [portSize, setPortSize] = useState(Settings.MaxPortCapacity);
const [terminalSize, setTerminalSize] = useState(Settings.MaxTerminalCapacity);
const [autosaveInterval, setAutosaveInterval] = useState(Settings.AutosaveInterval);
const [timestampFormat, setTimestampFormat] = useState(Settings.TimestampsFormat);
const [locale, setLocale] = useState(Settings.Locale);
const [diagnosticOpen, setDiagnosticOpen] = useState(false);
const [importSaveOpen, setImportSaveOpen] = useState(false);
const [importData, setImportData] = useState<ImportData | null>(null);
function handleExecTimeChange(event: any, newValue: number | number[]): void {
setExecTime(newValue as number);
Settings.CodeInstructionRunTime = newValue as number;
}
function handleRecentScriptsSizeChange(event: any, newValue: number | number[]): void {
setRecentScriptsSize(newValue as number);
Settings.MaxRecentScriptsCapacity = newValue as number;
}
function handleLogSizeChange(event: any, newValue: number | number[]): void {
setLogSize(newValue as number);
Settings.MaxLogCapacity = newValue as number;
}
function handlePortSizeChange(event: any, newValue: number | number[]): void {
setPortSize(newValue as number);
Settings.MaxPortCapacity = newValue as number;
}
function handleTerminalSizeChange(event: any, newValue: number | number[]): void {
setTerminalSize(newValue as number);
Settings.MaxTerminalCapacity = newValue as number;
}
function handleAutosaveIntervalChange(event: any, newValue: number | number[]): void {
setAutosaveInterval(newValue as number);
Settings.AutosaveInterval = newValue as number;
}
function handleLocaleChange(event: SelectChangeEvent<string>): void {
setLocale(event.target.value as string);
Settings.Locale = event.target.value as string;
}
function handleTimestampFormatChange(event: React.ChangeEvent<HTMLInputElement>): void {
setTimestampFormat(event.target.value);
Settings.TimestampsFormat = event.target.value;
}
function startImport(): void {
if (!window.File || !window.FileReader || !window.FileList || !window.Blob) return;
const ii = importInput.current;
if (ii === null) throw new Error("import input should not be null");
ii.click();
}
async function onImport(event: React.ChangeEvent<HTMLInputElement>): Promise<void> {
try {
const base64Save = await saveObject.getImportStringFromFile(event.target.files);
const data = await saveObject.getImportDataFromString(base64Save);
setImportData(data);
setImportSaveOpen(true);
} catch (ex: any) {
SnackbarEvents.emit(ex.toString(), ToastVariant.ERROR, 5000);
}
}
async function confirmedImportGame(): Promise<void> {
if (!importData) return;
try {
await saveObject.importGame(importData.base64);
} catch (ex: any) {
SnackbarEvents.emit(ex.toString(), ToastVariant.ERROR, 5000);
}
setImportSaveOpen(false);
setImportData(null);
}
function compareSaveGame(): void {
if (!importData) return;
props.router.toImportSave(importData.base64);
setImportSaveOpen(false);
setImportData(null);
}
return (
<div className={classes.root} style={{ width: "90%" }}>
<Typography variant="h4" gutterBottom>
Options
</Typography>
<Grid container spacing={3}>
<Grid item xs={12} sm={6}>
<List>
<ListItem>
<Box display="grid" sx={{ width: "fit-content", gridTemplateColumns: "1fr 3.5fr", gap: 1 }}>
<Tooltip
title={
<Typography>
The minimum number of milliseconds it takes to execute an operation in Netscript. Setting this too
low can result in poor performance if you have many scripts running.
</Typography>
}
>
<Typography>.script exec time (ms)</Typography>
</Tooltip>
<Slider
value={execTime}
onChange={handleExecTimeChange}
step={1}
min={5}
max={100}
valueLabelDisplay="auto"
/>
<Tooltip
title={
<Typography>
The maximum number of recently killed script entries being tracked. Setting this too high can
cause the game to use a lot of memory.
</Typography>
}
>
<Typography>Recently killed scripts size</Typography>
</Tooltip>
<Slider
value={recentScriptsSize}
onChange={handleRecentScriptsSizeChange}
step={25}
min={0}
max={500}
valueLabelDisplay="auto"
/>
<Tooltip
title={
<Typography>
The maximum number of lines a script's logs can hold. Setting this too high can cause the game to
use a lot of memory if you have many scripts running.
</Typography>
}
>
<Typography>Netscript log size</Typography>
</Tooltip>
<Slider
value={logSize}
onChange={handleLogSizeChange}
step={20}
min={20}
max={500}
valueLabelDisplay="auto"
/>
<Tooltip
title={
<Typography>
The maximum number of entries that can be written to a port using Netscript's write() function.
Setting this too high can cause the game to use a lot of memory.
</Typography>
}
>
<Typography>Netscript port size</Typography>
</Tooltip>
<Slider
value={portSize}
onChange={handlePortSizeChange}
step={1}
min={20}
max={100}
valueLabelDisplay="auto"
/>
<Tooltip
title={
<Typography>
The maximum number of entries that can be written to the terminal. Setting this too high can cause
the game to use a lot of memory.
</Typography>
}
>
<Typography>Terminal capacity</Typography>
</Tooltip>
<Slider
value={terminalSize}
onChange={handleTerminalSizeChange}
step={50}
min={50}
max={500}
valueLabelDisplay="auto"
marks
/>
<Tooltip
title={
<Typography>The time (in seconds) between each autosave. Set to 0 to disable autosave.</Typography>
}
>
<Typography>Autosave interval (s)</Typography>
</Tooltip>
<Slider
value={autosaveInterval}
onChange={handleAutosaveIntervalChange}
step={30}
min={0}
max={600}
valueLabelDisplay="auto"
marks
/>
</Box>
</ListItem>
<ListItem>
<OptionSwitch
checked={Settings.SuppressMessages}
onChange={(newValue) => (Settings.SuppressMessages = newValue)}
text="Suppress story messages"
tooltip={
<>
If this is set, then any messages you receive will not appear as popups on the screen. They will
still get sent to your home computer as '.msg' files and can be viewed with the 'cat' Terminal
command.
</>
}
/>
</ListItem>
<ListItem>
<OptionSwitch
checked={Settings.SuppressFactionInvites}
onChange={(newValue) => (Settings.SuppressFactionInvites = newValue)}
text="Suppress faction invites"
tooltip={
<>
If this is set, then any faction invites you receive will not appear as popups on the screen. Your
outstanding faction invites can be viewed in the 'Factions' page.
</>
}
/>
</ListItem>
<ListItem>
<OptionSwitch
checked={Settings.SuppressTravelConfirmation}
onChange={(newValue) => (Settings.SuppressTravelConfirmation = newValue)}
text="Suppress travel confirmations"
tooltip={
<>
If this is set, the confirmation message before traveling will not show up. You will automatically
be deducted the travel cost as soon as you click.
</>
}
/>
</ListItem>
<ListItem>
<OptionSwitch
checked={Settings.SuppressBuyAugmentationConfirmation}
onChange={(newValue) => (Settings.SuppressBuyAugmentationConfirmation = newValue)}
text="Suppress augmentations confirmation"
tooltip={<>If this is set, the confirmation message before buying augmentation will not show up.</>}
/>
</ListItem>
<ListItem>
<OptionSwitch
checked={Settings.SuppressTIXPopup}
onChange={(newValue) => (Settings.SuppressTIXPopup = newValue)}
text="Suppress TIX messages"
tooltip={<>If this is set, the stock market will never create any popup.</>}
/>
</ListItem>
{!!props.player.bladeburner && (
<ListItem>
<OptionSwitch
checked={Settings.SuppressBladeburnerPopup}
onChange={(newValue) => (Settings.SuppressBladeburnerPopup = newValue)}
text="Suppress bladeburner popup"
tooltip={
<>
If this is set, then having your Bladeburner actions interrupted by being busy with something else
will not display a popup message.
</>
}
/>
</ListItem>
)}
<ListItem>
<OptionSwitch
checked={Settings.SuppressSavedGameToast}
onChange={(newValue) => (Settings.SuppressSavedGameToast = newValue)}
text="Suppress Auto-Save Game Toast"
tooltip={<>If this is set, there will be no "Game Saved!" toast appearing after an auto-save.</>}
/>
</ListItem>
<ListItem>
<OptionSwitch
checked={Settings.SuppressAutosaveDisabledWarnings}
onChange={(newValue) => (Settings.SuppressAutosaveDisabledWarnings = newValue)}
text="Suppress Auto-Save Disabled Warning"
tooltip={<>If this is set, there will be no warning triggered when auto-save is disabled (at 0).</>}
/>
</ListItem>
<ListItem>
<OptionSwitch
checked={Settings.DisableHotkeys}
onChange={(newValue) => (Settings.DisableHotkeys = newValue)}
text="Disable hotkeys"
tooltip={
<>
If this is set, then most hotkeys (keyboard shortcuts) in the game are disabled. This includes
Terminal commands, hotkeys to navigate between different parts of the game, and the "Save and Close
(Ctrl + b)" hotkey in the Text Editor.
</>
}
/>
</ListItem>
<ListItem>
<OptionSwitch
checked={Settings.DisableASCIIArt}
onChange={(newValue) => (Settings.DisableASCIIArt = newValue)}
text="Disable ascii art"
tooltip={<>If this is set all ASCII art will be disabled.</>}
/>
</ListItem>
<ListItem>
<OptionSwitch
checked={Settings.DisableTextEffects}
onChange={(newValue) => (Settings.DisableTextEffects = newValue)}
text="Disable text effects"
tooltip={
<>
If this is set, text effects will not be displayed. This can help if text is difficult to read in
certain areas.
</>
}
/>
</ListItem>
<ListItem>
<OptionSwitch
checked={Settings.DisableOverviewProgressBars}
onChange={(newValue) => (Settings.DisableOverviewProgressBars = newValue)}
text="Disable Overview Progress Bars"
tooltip={<>If this is set, the progress bars in the character overview will be hidden.</>}
/>
</ListItem>
<ListItem>
<OptionSwitch
checked={Settings.EnableBashHotkeys}
onChange={(newValue) => (Settings.EnableBashHotkeys = newValue)}
text="Enable bash hotkeys"
tooltip={
<>
Improved Bash emulation mode. Setting this to 1 enables several new Terminal shortcuts and features
that more closely resemble a real Bash-style shell. Note that when this mode is enabled, the default
browser shortcuts are overriden by the new Bash shortcuts.
</>
}
/>
</ListItem>
<ListItem>
<OptionSwitch
checked={Settings.UseIEC60027_2}
onChange={(newValue) => (Settings.UseIEC60027_2 = newValue)}
text="Use GiB instead of GB"
tooltip={
<>
If this is set all references to memory will use GiB instead of GB, in accordance with IEC 60027-2.
</>
}
/>
</ListItem>
<ListItem>
<OptionSwitch
checked={Settings.ExcludeRunningScriptsFromSave}
onChange={(newValue) => (Settings.ExcludeRunningScriptsFromSave = newValue)}
text="Exclude Running Scripts from Save"
tooltip={
<>
If this is set, the save file will exclude all running scripts. This is only useful if your save is
lagging a lot. You'll have to restart your script every time you launch the game.
</>
}
/>
</ListItem>
<ListItem>
<Tooltip
title={
<Typography>
Terminal commands and log entries will be timestamped. See
https://date-fns.org/docs/Getting-Started/
</Typography>
}
>
<span>
<TextField
InputProps={{
startAdornment: (
<Typography
color={
formatTime(timestampFormat) === "format error" && timestampFormat !== ""
? "error"
: "success"
}
>
Timestamp&nbsp;format:&nbsp;
</Typography>
),
}}
value={timestampFormat}
onChange={handleTimestampFormatChange}
placeholder="yyyy-MM-dd hh:mm:ss"
/>
</span>
</Tooltip>
</ListItem>
<ListItem>
<OptionSwitch
checked={Settings.SaveGameOnFileSave}
onChange={(newValue) => (Settings.SaveGameOnFileSave = newValue)}
text="Save game on file save"
tooltip={<>Save your game any time a file is saved in the script editor.</>}
/>
</ListItem>
<ListItem>
<Tooltip title={<Typography>Sets the locale for displaying numbers.</Typography>}>
<Typography>Locale&nbsp;</Typography>
</Tooltip>
<Select value={locale} onChange={handleLocaleChange}>
<MenuItem value="en">en</MenuItem>
<MenuItem value="bg">bg</MenuItem>
<MenuItem value="cs">cs</MenuItem>
<MenuItem value="da-dk">da-dk</MenuItem>
<MenuItem value="de">de</MenuItem>
<MenuItem value="en-au">en-au</MenuItem>
<MenuItem value="en-gb">en-gb</MenuItem>
<MenuItem value="es">es</MenuItem>
<MenuItem value="fr">fr</MenuItem>
<MenuItem value="hu">hu</MenuItem>
<MenuItem value="it">it</MenuItem>
<MenuItem value="lv">lv</MenuItem>
<MenuItem value="no">no</MenuItem>
<MenuItem value="pl">pl</MenuItem>
<MenuItem value="ru">ru</MenuItem>
</Select>
</ListItem>
</List>
{!location.href.startsWith("file://") && (
<>
<ListItem>
<Typography>danielyxie / BigD (Original developer): </Typography>
<form action="https://www.paypal.com/cgi-bin/webscr" method="post" target="_blank">
<input type="hidden" name="cmd" value="_s-xclick" />
<input
type="hidden"
name="encrypted"
value="-----BEGIN PKCS7-----MIIHRwYJKoZIhvcNAQcEoIIHODCCBzQCAQExggEwMIIBLAIBADCBlDCBjjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtQYXlQYWwgSW5jLjETMBEGA1UECxQKbGl2ZV9jZXJ0czERMA8GA1UEAxQIbGl2ZV9hcGkxHDAaBgkqhkiG9w0BCQEWDXJlQHBheXBhbC5jb20CAQAwDQYJKoZIhvcNAQEBBQAEgYA2Y2VGE75oWct89z//G2YEJKmzx0uDTXNrpje9ThxmUnBLFZCY+I11Pors7lGRvFqo5okwnu41CfYMPHDxpAgyYyQndMX9pWUX0gLfBMm2BaHwsNBCwt34WmpQqj7TGsQ+aw9NbmkxiJltGnOa+6/gy10mPZAA3HxiieLeCKkGgDELMAkGBSsOAwIaBQAwgcQGCSqGSIb3DQEHATAUBggqhkiG9w0DBwQI72F1YSzHUd2AgaDMekHU3AKT93Ey9wkB3486bV+ngFSD6VOHrPweH9QATsp+PMe9QM9vmq+s2bGtTbZaYrFqM3M97SnQ0l7IQ5yuOzdZhRdfysu5uJ8dnuHUzq4gLSzqMnZ6/3c+PoHB8AS1nYHUVL4U0+ogZsO1s97IAQyfck9SaoFlxVtqQhkb8752MkQJJvGu3ZQSQGcVC4hFDPk8prXqyq4BU/k/EliwoIIDhzCCA4MwggLsoAMCAQICAQAwDQYJKoZIhvcNAQEFBQAwgY4xCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEUMBIGA1UEChMLUGF5UGFsIEluYy4xEzARBgNVBAsUCmxpdmVfY2VydHMxETAPBgNVBAMUCGxpdmVfYXBpMRwwGgYJKoZIhvcNAQkBFg1yZUBwYXlwYWwuY29tMB4XDTA0MDIxMzEwMTMxNVoXDTM1MDIxMzEwMTMxNVowgY4xCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEUMBIGA1UEChMLUGF5UGFsIEluYy4xEzARBgNVBAsUCmxpdmVfY2VydHMxETAPBgNVBAMUCGxpdmVfYXBpMRwwGgYJKoZIhvcNAQkBFg1yZUBwYXlwYWwuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDBR07d/ETMS1ycjtkpkvjXZe9k+6CieLuLsPumsJ7QC1odNz3sJiCbs2wC0nLE0uLGaEtXynIgRqIddYCHx88pb5HTXv4SZeuv0Rqq4+axW9PLAAATU8w04qqjaSXgbGLP3NmohqM6bV9kZZwZLR/klDaQGo1u9uDb9lr4Yn+rBQIDAQABo4HuMIHrMB0GA1UdDgQWBBSWn3y7xm8XvVk/UtcKG+wQ1mSUazCBuwYDVR0jBIGzMIGwgBSWn3y7xm8XvVk/UtcKG+wQ1mSUa6GBlKSBkTCBjjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtQYXlQYWwgSW5jLjETMBEGA1UECxQKbGl2ZV9jZXJ0czERMA8GA1UEAxQIbGl2ZV9hcGkxHDAaBgkqhkiG9w0BCQEWDXJlQHBheXBhbC5jb22CAQAwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOBgQCBXzpWmoBa5e9fo6ujionW1hUhPkOBakTr3YCDjbYfvJEiv/2P+IobhOGJr85+XHhN0v4gUkEDI8r2/rNk1m0GA8HKddvTjyGw/XqXa+LSTlDYkqI8OwR8GEYj4efEtcRpRYBxV8KxAW93YDWzFGvruKnnLbDAF6VR5w/cCMn5hzGCAZowggGWAgEBMIGUMIGOMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDU1vdW50YWluIFZpZXcxFDASBgNVBAoTC1BheVBhbCBJbmMuMRMwEQYDVQQLFApsaXZlX2NlcnRzMREwDwYDVQQDFAhsaXZlX2FwaTEcMBoGCSqGSIb3DQEJARYNcmVAcGF5cGFsLmNvbQIBADAJBgUrDgMCGgUAoF0wGAYJKoZIhvcNAQkDMQsGCSqGSIb3DQEHATAcBgkqhkiG9w0BCQUxDxcNMTcwNzI1MDExODE2WjAjBgkqhkiG9w0BCQQxFgQUNo8efiZ7sk7nwKM/6B6Z7sU8hIIwDQYJKoZIhvcNAQEBBQAEgYB+JB4vZ/r48815/1HF/xK3+rOx7bPz3kAXmbhW/mkoF4OUbzqMeljvDIA9q/BDdlCLtxFOw9XlftTzv0eZCW/uCIiwu5wTzPIfPY1SI8WHe4cJbP2f2EYxIVs8D7OSirbW4yVa0+gACaLLj0rzIzNN8P/5PxgB03D+jwkcJABqng==-----END PKCS7-----"
/>
<input
type="image"
src="https://www.paypalobjects.com/en_US/i/btn/btn_donateCC_LG.gif"
name="submit"
alt="PayPal - The safer, easier way to pay online!"
/>
</form>
</ListItem>
<ListItem>
<Typography>
hydroflame (Current maintainer):{" "}
<Link href="https://www.google.com/search?q=Where+to+donate+blood+near+me%3F" target="_blank">
Donate blood!
</Link>{" "}
</Typography>
</ListItem>
</>
)}
</Grid>
<Box sx={{ display: "grid", width: "fit-content", height: "fit-content" }}>
<Box sx={{ display: "grid", gridTemplateColumns: "1fr 1fr" }}>
<Button onClick={() => props.save()} startIcon={<SaveIcon />}>
Save Game
</Button>
<DeleteGameButton />
</Box>
<Box sx={{ display: "grid", gridTemplateColumns: "1fr 1fr" }}>
<Tooltip title={<Typography>Export your game to a text file.</Typography>}>
<Button onClick={() => props.export()} startIcon={<DownloadIcon />}>
Export Game
</Button>
</Tooltip>
<Tooltip
title={
<Typography>
Import your game from a text file.
<br />
This will <strong>overwrite</strong> your current game. Back it up first!
</Typography>
}
>
<Button onClick={startImport} startIcon={<UploadIcon />}>
Import Game
<input ref={importInput} id="import-game-file-selector" type="file" hidden onChange={onImport} />
</Button>
</Tooltip>
<ConfirmationModal
open={importSaveOpen}
onClose={() => setImportSaveOpen(false)}
onConfirm={() => confirmedImportGame()}
additionalButton={<Button onClick={compareSaveGame}>Compare Save</Button>}
confirmationText={
<>
Importing a new game will <strong>completely wipe</strong> the current data!
<br />
<br />
Make sure to have a backup of your current save file before importing.
<br />
The file you are attempting to import seems valid.
{(importData?.playerData?.lastSave ?? 0) > 0 && (
<>
<br />
<br />
The export date of the save file is{" "}
<strong>{new Date(importData?.playerData?.lastSave ?? 0).toLocaleString()}</strong>
</>
)}
{(importData?.playerData?.totalPlaytime ?? 0) > 0 && (
<>
<br />
<br />
Total play time of imported game:{" "}
{convertTimeMsToTimeElapsedString(importData?.playerData?.totalPlaytime ?? 0)}
</>
)}
<br />
<br />
</>
}
/>
</Box>
<Box sx={{ display: "grid" }}>
<Tooltip
title={
<Typography>
Forcefully kill all active running scripts, in case there is a bug or some unexpected issue with the
game. After using this, save the game and then reload the page. This is different then normal kill in
that normal kill will tell the script to shut down while force kill just removes the references to it
(and it should crash on it's own). This will not remove the files on your computer. Just forcefully
kill all running instance of all scripts.
</Typography>
}
>
<Button onClick={() => props.forceKill()}>Force kill all active scripts</Button>
</Tooltip>
</Box>
<Box sx={{ display: "grid", gridTemplateColumns: "1fr 1fr" }}>
<SoftResetButton
noConfirmation={Settings.SuppressBuyAugmentationConfirmation}
onTriggered={props.softReset}
/>
<Tooltip
title={
<Typography>
If your save file is extremely big you can use this button to view a map of all the files on every
server. Be careful there might be spoilers.
</Typography>
}
>
<Button onClick={() => setDiagnosticOpen(true)}>Diagnose files</Button>
</Tooltip>
</Box>
<Box sx={{ display: "grid", gridTemplateColumns: "1fr 1fr 1fr" }}>
<Tooltip title="Head to the theme browser to see a collection of prebuilt themes.">
<Button startIcon={<PaletteIcon />} onClick={() => props.router.toThemeBrowser()}>
Theme Browser
</Button>
</Tooltip>
<ThemeEditorButton router={props.router} />
<StyleEditorButton />
</Box>
<Box>
<Link href="https://github.com/danielyxie/bitburner/issues/new" target="_blank">
<Typography>Report bug</Typography>
</Link>
<Link href="https://bitburner.readthedocs.io/en/latest/changelog.html" target="_blank">
<Typography>Changelog</Typography>
</Link>
<Link href="https://bitburner.readthedocs.io/en/latest/index.html" target="_blank">
<Typography>Documentation</Typography>
</Link>
<Link href="https://discord.gg/TFc3hKD" target="_blank">
<Typography>Discord</Typography>
</Link>
<Link href="https://www.reddit.com/r/bitburner" target="_blank">
<Typography>Reddit</Typography>
</Link>
<Link href="https://plaza.dsolver.ca/games/bitburner" target="_blank">
<Typography>Incremental game plaza</Typography>
</Link>
</Box>
</Box>
</Grid>
<FileDiagnosticModal open={diagnosticOpen} onClose={() => setDiagnosticOpen(false)} />
</div>
);
}

@ -96,6 +96,9 @@ const useStyles = makeStyles((theme: Theme) =>
display: "flex",
flexDirection: "column-reverse",
},
titleButton: {
padding: "1px 6px",
},
success: {
color: theme.colors.success,
},
@ -260,16 +263,25 @@ function LogWindow(props: IProps): React.ReactElement {
}}
>
<Box className="drag" display="flex" alignItems="center" ref={draggableRef}>
<Typography color="primary" variant="h6" title={title(true)}>
<Typography color="primary" variant="h6" sx={{ marginRight: "auto" }} title={title(true)}>
{title()}
</Typography>
<Box position="absolute" right={0}>
{!workerScripts.has(script.pid) && <Button onClick={run}>Run</Button>}
{workerScripts.has(script.pid) && <Button onClick={kill}>Kill</Button>}
<Button onClick={minimize}>{minimized ? "\u{1F5D6}" : "\u{1F5D5}"}</Button>
<Button onClick={props.onClose}>Close</Button>
</Box>
{!workerScripts.has(script.pid) ? (
<Button className={classes.titleButton} onClick={run} onTouchEnd={run}>
Run
</Button>
) : (
<Button className={classes.titleButton} onClick={kill} onTouchEnd={kill}>
Kill
</Button>
)}
<Button className={classes.titleButton} onClick={minimize} onTouchEnd={minimize}>
{minimized ? "\u{1F5D6}" : "\u{1F5D5}"}
</Button>
<Button className={classes.titleButton} onClick={props.onClose} onTouchEnd={props.onClose}>
Close
</Button>
</Box>
</Paper>
<Paper sx={{ overflow: "scroll", overflowWrap: "break-word", whiteSpace: "pre-wrap" }}>
@ -277,6 +289,7 @@ function LogWindow(props: IProps): React.ReactElement {
className={classes.logs}
height={500}
width={500}
minConstraints={[250, 30]}
handle={
<span style={{ position: "absolute", right: "-10px", bottom: "-13px", cursor: "nw-resize" }}>
<ArrowForwardIosIcon color="primary" style={{ transform: "rotate(45deg)" }} />

@ -130,7 +130,14 @@ export function Overview({ children, mode }: IProps): React.ReactElement {
size="small"
className={classes.visibilityToggle}
>
{<CurrentIcon className={classes.icon} color="secondary" onClick={() => setOpen((old) => !old)} />}
{
<CurrentIcon
className={classes.icon}
color="secondary"
onClick={() => setOpen((old) => !old)}
onTouchEnd={() => setOpen((old) => !old)}
/>
}
</Button>
</Box>
</Box>

@ -223,19 +223,21 @@ class NumeralFormatter {
const parsed = parseFloat(s);
const selfParsed = this.parseCustomLargeNumber(s);
// Check for one or more NaN values
if (isNaN(parsed) && numeralValue === null && isNaN(selfParsed)) {
if (isNaN(parsed) && isNaN(selfParsed)) {
if (numeralValue === null) {
// 3x NaN
return NaN;
} else if (isNaN(parsed) && isNaN(selfParsed)) {
}
// 2x NaN
return numeralValue;
} else if (numeralValue === null && isNaN(selfParsed)) {
// 2x NaN
return parsed;
} else if (isNaN(parsed) && numeralValue === null) {
} else if (isNaN(parsed)) {
if (numeralValue === null) {
// 2x NaN
return selfParsed;
} else if (isNaN(parsed)) {
}
// 1x NaN
return this.largestAbsoluteNumber(numeralValue, selfParsed);
} else if (numeralValue === null) {

@ -0,0 +1,193 @@
// choose random character for generating plaintexts to compress
export function comprGenChar(): string {
const r = Math.random();
if (r < 0.4) {
return "ABCDEFGHIJKLMNOPQRSTUVWXYZ"[Math.floor(26 * Math.random())];
} else if (r < 0.8) {
return "abcdefghijklmnopqrstuvwxyz"[Math.floor(26 * Math.random())];
} else {
return "01234567689"[Math.floor(10 * Math.random())];
}
}
// generate plaintext which is amenable to LZ encoding
export function comprLZGenerate(): string {
const length = 50 + Math.floor(25 * (Math.random() + Math.random()));
let plain = "";
while (plain.length < length) {
if (Math.random() < 0.8) {
plain += comprGenChar();
} else {
const length = 1 + Math.floor(9 * Math.random());
const offset = 1 + Math.floor(9 * Math.random());
if (offset > plain.length) {
continue;
}
for (let i = 0; i < length; ++i) {
plain += plain[plain.length - offset];
}
}
}
return plain.substring(0, length);
}
// compress plaintest string
export function comprLZEncode(plain: string): string {
// for state[i][j]:
// if i is 0, we're adding a literal of length j
// else, we're adding a backreference of offset i and length j
let cur_state: (string | null)[][] = Array.from(Array(10), () => Array(10).fill(null));
let new_state: (string | null)[][] = Array.from(Array(10), () => Array(10));
function set(state: (string | null)[][], i: number, j: number, str: string): void {
const current = state[i][j];
if (current == null || str.length < current.length) {
state[i][j] = str;
} else if (str.length === current.length && Math.random() < 0.5) {
// if two strings are the same length, pick randomly so that
// we generate more possible inputs to Compression II
state[i][j] = str;
}
}
// initial state is a literal of length 1
cur_state[0][1] = "";
for (let i = 1; i < plain.length; ++i) {
for (const row of new_state) {
row.fill(null);
}
const c = plain[i];
// handle literals
for (let length = 1; length <= 9; ++length) {
const string = cur_state[0][length];
if (string == null) {
continue;
}
if (length < 9) {
// extend current literal
set(new_state, 0, length + 1, string);
} else {
// start new literal
set(new_state, 0, 1, string + "9" + plain.substring(i - 9, i) + "0");
}
for (let offset = 1; offset <= Math.min(9, i); ++offset) {
if (plain[i - offset] === c) {
// start new backreference
set(new_state, offset, 1, string + length + plain.substring(i - length, i));
}
}
}
// handle backreferences
for (let offset = 1; offset <= 9; ++offset) {
for (let length = 1; length <= 9; ++length) {
const string = cur_state[offset][length];
if (string == null) {
continue;
}
if (plain[i - offset] === c) {
if (length < 9) {
// extend current backreference
set(new_state, offset, length + 1, string);
} else {
// start new backreference
set(new_state, offset, 1, string + "9" + offset + "0");
}
}
// start new literal
set(new_state, 0, 1, string + length + offset);
}
}
const tmp_state = new_state;
new_state = cur_state;
cur_state = tmp_state;
}
let result = null;
for (let len = 1; len <= 9; ++len) {
let string = cur_state[0][len];
if (string == null) {
continue;
}
string += len + plain.substring(plain.length - len, plain.length);
if (result == null || string.length < result.length) {
result = string;
} else if (string.length == result.length && Math.random() < 0.5) {
result = string;
}
}
for (let offset = 1; offset <= 9; ++offset) {
for (let len = 1; len <= 9; ++len) {
let string = cur_state[offset][len];
if (string == null) {
continue;
}
string += len + "" + offset;
if (result == null || string.length < result.length) {
result = string;
} else if (string.length == result.length && Math.random() < 0.5) {
result = string;
}
}
}
return result ?? "";
}
// decompress LZ-compressed string, or return null if input is invalid
export function comprLZDecode(compr: string): string | null {
let plain = "";
for (let i = 0; i < compr.length; ) {
const literal_length = compr.charCodeAt(i) - 0x30;
if (literal_length < 0 || literal_length > 9 || i + 1 + literal_length > compr.length) {
return null;
}
plain += compr.substring(i + 1, i + 1 + literal_length);
i += 1 + literal_length;
if (i >= compr.length) {
break;
}
const backref_length = compr.charCodeAt(i) - 0x30;
if (backref_length < 0 || backref_length > 9) {
return null;
} else if (backref_length === 0) {
++i;
} else {
if (i + 1 >= compr.length) {
return null;
}
const backref_offset = compr.charCodeAt(i + 1) - 0x30;
if ((backref_length > 0 && (backref_offset < 1 || backref_offset > 9)) || backref_offset > plain.length) {
return null;
}
for (let j = 0; j < backref_length; ++j) {
plain += plain[plain.length - backref_offset];
}
i += 2;
}
}
return plain;
}

@ -30,17 +30,17 @@ describe("Numeral formatting for positive numbers", () => {
expect(numeralWrapper.formatReallyBigNumber(987654321)).toEqual("987.654m");
expect(numeralWrapper.formatReallyBigNumber(987654321987)).toEqual("987.654b");
expect(numeralWrapper.formatReallyBigNumber(987654321987654)).toEqual("987.654t");
expect(numeralWrapper.formatReallyBigNumber(987654321987654321)).toEqual("987.654q");
expect(numeralWrapper.formatReallyBigNumber(987654321987654321987)).toEqual("987.654Q");
expect(numeralWrapper.formatReallyBigNumber(987654321987654321987654)).toEqual("987.654s");
expect(numeralWrapper.formatReallyBigNumber(987654321987654321987654321)).toEqual("987.654S");
expect(numeralWrapper.formatReallyBigNumber(987654321987654321987654321987)).toEqual("987.654o");
expect(numeralWrapper.formatReallyBigNumber(987654321987654321987654321987654)).toEqual("987.654n");
expect(numeralWrapper.formatReallyBigNumber(987654321987654000)).toEqual("987.654q");
expect(numeralWrapper.formatReallyBigNumber(987654321987654000000)).toEqual("987.654Q");
expect(numeralWrapper.formatReallyBigNumber(987654321987654000000000)).toEqual("987.654s");
expect(numeralWrapper.formatReallyBigNumber(987654321987654000000000000)).toEqual("987.654S");
expect(numeralWrapper.formatReallyBigNumber(987654321987654000000000000000)).toEqual("987.654o");
expect(numeralWrapper.formatReallyBigNumber(987654321987654000000000000000000)).toEqual("987.654n");
});
test("should format even bigger really big numbers in scientific format", () => {
expect(numeralWrapper.formatReallyBigNumber(987654321987654321987654321987654321)).toEqual("9.877e+35");
expect(numeralWrapper.formatReallyBigNumber(9876543219876543219876543219876543219)).toEqual("9.877e+36");
expect(numeralWrapper.formatReallyBigNumber(98765432198765432198765432198765432198)).toEqual("9.877e+37");
expect(numeralWrapper.formatReallyBigNumber(987654321987654000000000000000000000)).toEqual("9.877e+35");
expect(numeralWrapper.formatReallyBigNumber(9876543219876540000000000000000000000)).toEqual("9.877e+36");
expect(numeralWrapper.formatReallyBigNumber(98765432198765400000000000000000000000)).toEqual("9.877e+37");
});
test("should format percentage", () => {
expect(numeralWrapper.formatPercentage(1234.56789)).toEqual("123456.79%");
@ -74,17 +74,17 @@ describe("Numeral formatting for negative numbers", () => {
expect(numeralWrapper.formatReallyBigNumber(-987654321)).toEqual("-987.654m");
expect(numeralWrapper.formatReallyBigNumber(-987654321987)).toEqual("-987.654b");
expect(numeralWrapper.formatReallyBigNumber(-987654321987654)).toEqual("-987.654t");
expect(numeralWrapper.formatReallyBigNumber(-987654321987654321)).toEqual("-987.654q");
expect(numeralWrapper.formatReallyBigNumber(-987654321987654321987)).toEqual("-987.654Q");
expect(numeralWrapper.formatReallyBigNumber(-987654321987654321987654)).toEqual("-987.654s");
expect(numeralWrapper.formatReallyBigNumber(-987654321987654321987654321)).toEqual("-987.654S");
expect(numeralWrapper.formatReallyBigNumber(-987654321987654321987654321987)).toEqual("-987.654o");
expect(numeralWrapper.formatReallyBigNumber(-987654321987654321987654321987654)).toEqual("-987.654n");
expect(numeralWrapper.formatReallyBigNumber(-987654321987654000)).toEqual("-987.654q");
expect(numeralWrapper.formatReallyBigNumber(-987654321987654000000)).toEqual("-987.654Q");
expect(numeralWrapper.formatReallyBigNumber(-987654321987654000000000)).toEqual("-987.654s");
expect(numeralWrapper.formatReallyBigNumber(-987654321987654000000000000)).toEqual("-987.654S");
expect(numeralWrapper.formatReallyBigNumber(-987654321987654000000000000000)).toEqual("-987.654o");
expect(numeralWrapper.formatReallyBigNumber(-987654321987654000000000000000000)).toEqual("-987.654n");
});
test("should format even bigger really big numbers in scientific format", () => {
expect(numeralWrapper.formatReallyBigNumber(-987654321987654321987654321987654321)).toEqual("-9.877e+35");
expect(numeralWrapper.formatReallyBigNumber(-9876543219876543219876543219876543219)).toEqual("-9.877e+36");
expect(numeralWrapper.formatReallyBigNumber(-98765432198765432198765432198765432198)).toEqual("-9.877e+37");
expect(numeralWrapper.formatReallyBigNumber(-987654321987654000000000000000000000)).toEqual("-9.877e+35");
expect(numeralWrapper.formatReallyBigNumber(-9876543219876540000000000000000000000)).toEqual("-9.877e+36");
expect(numeralWrapper.formatReallyBigNumber(-98765432198765400000000000000000000000)).toEqual("-9.877e+37");
});
test("should format percentage", () => {
expect(numeralWrapper.formatPercentage(-1234.56789)).toEqual("-123456.79%");

@ -93,15 +93,6 @@ module.exports = (env, argv) => {
new webpack.DefinePlugin({
"process.env.NODE_ENV": isDevelopment ? '"development"' : '"production"',
}),
// http://stackoverflow.com/questions/29080148/expose-jquery-to-real-window-object-with-webpack
new webpack.ProvidePlugin({
// Automtically detect jQuery and $ as free var in modules
// and inject the jquery library
// This is required by many jquery plugins
jquery: "jquery",
jQuery: "jquery",
$: "jquery",
}),
new HtmlWebpackPlugin(htmlConfig),
new MiniCssExtractPlugin({
filename: "[name].css",