Merge branch 'dev' into breaking-contract-capitalization

This commit is contained in:
hydroflame 2022-04-26 11:15:37 -04:00 committed by GitHub
commit 44475aaac9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
62 changed files with 1557 additions and 471 deletions

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,176 +84,289 @@ 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 The list contains the name of (i.e. the value returned by
:js:func:`getContractType`) and a brief summary of the problem it poses. :js:func:`getContractType`) and a brief summary of the problem it poses.
+------------------------------------+------------------------------------------------------------------------------------------+ +-----------------------------------------+------------------------------------------------------------------------------------------+
| Name | Problem Summary | | Name | Problem Summary |
+====================================+==========================================================================================+ +=========================================+==========================================================================================+
| Find Largest Prime Factor | | Given a number, find its largest prime factor. A prime factor | | Find Largest Prime Factor | | Given a number, find its largest prime factor. A prime factor |
| | | is a factor that is a prime number. | | | | is a factor that is a prime number. |
+------------------------------------+------------------------------------------------------------------------------------------+ +-----------------------------------------+------------------------------------------------------------------------------------------+
| Subarray with Maximum Sum | | Given an array of integers, find the contiguous subarray (containing | | 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. | | | | 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 | | 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? | | | | 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. | | 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. | | | | 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 | | | | How many different distinct ways can that number n be written as |
| | | a sum of integers contained in the given set? | | | | a sum of integers contained in the given set? |
| | | You may use each integer in the set zero or more times. | | | | 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 | | Spiralize Matrix | | Given an array of array of numbers representing a 2D matrix, return the |
| | | elements of that matrix in clockwise spiral order. | | | | elements of that matrix in clockwise spiral order. |
| | | | | | | |
| | | Example: The spiral order of | | | | Example: The spiral order of |
| | | | | | | |
| | | [1, 2, 3, 4] | | | | [1, 2, 3, 4] |
| | | [5, 6, 7, 8] | | | | [5, 6, 7, 8] |
| | | [9, 10, 11, 12] | | | | [9, 10, 11, 12] |
| | | | | | | |
| | | is [1, 2, 3, 4, 8, 12, 11, 10, 9, 5, 6, 7] | | | | 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 | | 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 | | | | 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 | | | | are at position i and your maximum jump length is n, then you can jump |
| | | to any position from i to i+n. | | | | to any position from i to i+n. |
| | | | | | | |
| | | Assuming you are initially positioned at the start of the array, determine | | | | Assuming you are initially positioned at the start of the array, determine |
| | | whether you are able to reach the last index of the array. | | | | 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 | | 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 | | | | 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 | | | | are at position i and your maximum jump length is n, then you can jump |
| | | to any position from i to i+n. | | | | to any position from i to i+n. |
| | | | | | | |
| | | Assuming you are initially positioned at the start of the array, determine | | | | Assuming you are initially positioned at the start of the array, determine |
| | | the minimum number of jumps to reach the end of the array. | | | | 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. | | | | 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 | | 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 | | | | is an array with two numbers, where the first number is always less than |
| | | the second (e.g. [1, 5]). | | | | the second (e.g. [1, 5]). |
| | | | | | | |
| | | The intervals must be returned in ASCENDING order. | | | | The intervals must be returned in ASCENDING order. |
| | | | | | | |
| | | Example: | | | | Example: |
| | | [[1, 3], [8, 10], [2, 6], [10, 16]] | | | | [[1, 3], [8, 10], [2, 6], [10, 16]] |
| | | merges into [[1, 6], [8, 16]] | | | | merges into [[1, 6], [8, 16]] |
+------------------------------------+------------------------------------------------------------------------------------------+ +-----------------------------------------+------------------------------------------------------------------------------------------+
| Generate IP Addresses | | Given a string containing only digits, return an array with all possible | | 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. | | | | valid IP address combinations that can be created from the string. |
| | | | | | | |
| | | An octet in the IP address cannot begin with '0' unless the number itself | | | | An octet in the IP address cannot begin with '0' unless the number itself |
| | | is actually 0. For example, "192.168.010.1" is NOT a valid IP. | | | | is actually 0. For example, "192.168.010.1" is NOT a valid IP. |
| | | | | | | |
| | | Examples: | | | | Examples: |
| | | 25525511135 -> [255.255.11.135, 255.255.111.35] | | | | 25525511135 -> [255.255.11.135, 255.255.111.35] |
| | | 1938718066 -> [193.87.180.66] | | | | 1938718066 -> [193.87.180.66] |
+------------------------------------+------------------------------------------------------------------------------------------+ +-----------------------------------------+------------------------------------------------------------------------------------------+
| Algorithmic Stock Trader I | | You are given an array of numbers representing stock prices, where the | | 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. | | | | i-th element represents the stock price on day i. |
| | | | | | | |
| | | Determine the maximum possible profit you can earn using at most one | | | | Determine the maximum possible profit you can earn using at most one |
| | | transaction (i.e. you can buy an sell the stock once). If no profit | | | | 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 | | | | can be made, then the answer should be 0. Note that you must buy the stock |
| | | before you can sell it. | | | | before you can sell it. |
+------------------------------------+------------------------------------------------------------------------------------------+ +-----------------------------------------+------------------------------------------------------------------------------------------+
| Algorithmic Stock Trader II | | You are given an array of numbers representing stock prices, where the | | 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. | | | | i-th element represents the stock price on day i. |
| | | | | | | |
| | | Determine the maximum possible profit you can earn using as many transactions | | | | Determine the maximum possible profit you can earn using as many transactions |
| | | as you'd like. A transaction is defined as buying and then selling one | | | | as you'd like. A transaction is defined as buying and then selling one |
| | | share of the stock. Note that you cannot engage in multiple transactions at | | | | 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 | | | | 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. | | | | 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 | | 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. | | | | i-th element represents the stock price on day i. |
| | | | | | | |
| | | Determine the maximum possible profit you can earn using at most two | | | | Determine the maximum possible profit you can earn using at most two |
| | | transactions. A transaction is defined as buying and then selling one share | | | | transactions. A transaction is defined as buying and then selling one share |
| | | of the stock. Note that you cannot engage in multiple transactions at once. | | | | 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 | | | | 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. | | | | 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. | | 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 | | | | The second element is an array of numbers representing stock prices, where the |
| | | i-th element represents the stock price on day i. | | | | i-th element represents the stock price on day i. |
| | | | | | | |
| | | Determine the maximum possible profit you can earn using at most k transactions. | | | | Determine the maximum possible profit you can earn using at most k transactions. |
| | | A transaction is defined as buying and then selling one share of the stock. | | | | A transaction is defined as buying and then selling one share of the stock. |
| | | Note that you cannot engage in multiple transactions at once. In other words, | | | | 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 | | | | you must sell the stock before you can buy it. If no profit can be made, then |
| | | the answer should be 0. | | | | 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 | | 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 | | | | 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 | | | | 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 | | | | bottom of the triangle. In each step of the path, you may only move to adjacent |
| | | numbers in the row below. | | | | 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 | | 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 | | | | 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, | | | | grid and that you are trying to reach the bottom-right corner. On each step, |
| | | you may only move down or to the right. | | | | you may only move down or to the right. |
| | | | | | | |
| | | | | | |
| | | Determine how many unique paths there are from start to finish. | | | | 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 | | 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 | | | | a grid. The 2D array contains 1's and 0's, where 1 represents an obstacle and |
| | | | | | |
| | | 0 represents a free space. | | | | 0 represents a free space. |
| | | | | | | |
| | | Assume you are initially positioned in top-left corner of that grid and that you | | | | Assume you are initially positioned in top-left corner of that grid and that you |
| | | are trying to reach the bottom-right corner. In each step, you may only move down | | | | are trying to reach the bottom-right corner. In each step, you may only move down |
| | | or to the right. Furthermore, you cannot move onto spaces which have obstacles. | | | | or to the right. Furthermore, you cannot move onto spaces which have obstacles. |
| | | | | | | |
| | | Determine how many unique paths there are from start to finish. | | | | 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 | | 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 | | | | a grid. The 2D array contains 1's and 0's, where 1 represents an obstacle and |
| | | 0 represents a free space. | | | | 0 represents a free space. |
| | | | | | | |
| | | Assume you are initially positioned in top-left corner of that grid and that you | | | | Assume you are initially positioned in top-left corner of that grid and that you |
| | | are trying to reach the bottom-right corner. In each step, you may move to the up, | | | | are trying to reach the bottom-right corner. In each step, you may move to the up, |
| | | down, left or right. Furthermore, you cannot move onto spaces which have obstacles. | | | | down, left or right. Furthermore, you cannot move onto spaces which have obstacles. |
| | | | | | | |
| | | Determine if paths exist from start to destination, and find the shortest one. | | | | Determine if paths exist from start to destination, and find the shortest one. |
| | | | | | | |
| | | Examples: | | | | Examples: |
| | | [[0,1,0,0,0], | | | | [[0,1,0,0,0], |
| | | [0,0,0,1,0]] -> "DRRURRD" | | | | [0,0,0,1,0]] -> "DRRURRD" |
| | | [[0,1], | | | | [[0,1], |
| | | [1,0]] -> "" | | | | [1,0]] -> "" |
| | | | | | | |
+------------------------------------+------------------------------------------------------------------------------------------+ +-----------------------------------------+------------------------------------------------------------------------------------------+
| Sanitize Parentheses in Expression | | Given a string with parentheses and letters, remove the minimum number of invalid | | 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 | | | | parentheses in order to validate the string. If there are multiple minimal ways |
| | | to validate the string, provide all of the possible results. | | | | to validate the string, provide all of the possible results. |
| | | | | | | |
| | | The answer should be provided as an array of strings. If it is impossible to validate | | | | The answer should be provided as an array of strings. If it is impossible to validate |
| | | the string, the result should be an array with only an empty string. | | | | the string, the result should be an array with only an empty string. |
| | | | | | | |
| | | Examples: | | | | Examples: |
| | | ()())() -> [()()(), (())()] | | | | ()())() -> [()()(), (())()] |
| | | (a)())() -> [(a)()(), (a())()] | | | | (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 | | 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 | | | | number. Return all possible ways you can add the +, -, and * operators to the string |
| | | of digits such that it evaluates to the target number. | | | | of digits such that it evaluates to the target number. |
| | | | | | | |
| | | The answer should be provided as an array of strings containing the valid expressions. | | | | The answer should be provided as an array of strings containing the valid expressions. |
| | | | | | | |
| | | NOTE: Numbers in an expression cannot have leading 0's | | | | NOTE: Numbers in an expression cannot have leading 0's |
| | | NOTE: The order of evaluation expects script operator precedence | | | | NOTE: The order of evaluation expects script operator precedence |
| | | | | | | |
| | | Examples: | | | | Examples: |
| | | Input: digits = "123", target = 6 | | | | Input: digits = "123", target = 6 |
| | | Output: [1+2+3, 1*2*3] | | | | Output: [1+2+3, 1*2*3] |
| | | | | | | |
| | | Input: digits = "105", target = 5 | | | | Input: digits = "105", target = 5 |
| | | Output: [1*0+5, 10-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: In this BitNode:
* Your stats are significantly decreased * Your stats are significantly decreased
* You cannnot purchase additional servers * You cannot purchase additional servers
* Hacking is significantly less profitable * Hacking is significantly less profitable
Source-File Source-File

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

@ -8,6 +8,7 @@ import { numeralWrapper } from "../ui/numeralFormat";
import { Money } from "../ui/React/Money"; import { Money } from "../ui/React/Money";
import { Generic_fromJSON, Generic_toJSON, Reviver } from "../utils/JSONReviver"; import { Generic_fromJSON, Generic_toJSON, Reviver } from "../utils/JSONReviver";
import { FactionNames } from "../Faction/data/FactionNames";
export interface IConstructorParams { export interface IConstructorParams {
info: string | JSX.Element; info: string | JSX.Element;
@ -49,6 +50,12 @@ export interface IConstructorParams {
bladeburner_stamina_gain_mult?: number; bladeburner_stamina_gain_mult?: number;
bladeburner_analysis_mult?: number; bladeburner_analysis_mult?: number;
bladeburner_success_chance_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; startingMoney?: number;
programs?: string[]; 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 <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) if (startingMoney)
desc = ( desc = (
@ -390,6 +441,9 @@ export class Augmentation {
// Initial cost. Doesn't change when you purchase multiple Augmentation // Initial cost. Doesn't change when you purchase multiple Augmentation
startingCost = 0; startingCost = 0;
// Initial rep requirement. Doesn't change when you purchase multiple Augmentation
startingRepRequirement = 0;
// Factions that offer this aug. // Factions that offer this aug.
factions: string[] = []; factions: string[] = [];
@ -409,6 +463,7 @@ export class Augmentation {
this.baseRepRequirement = params.repCost; this.baseRepRequirement = params.repCost;
this.baseCost = params.moneyCost; this.baseCost = params.moneyCost;
this.startingCost = this.baseCost; this.startingCost = this.baseCost;
this.startingRepRequirement = this.baseRepRequirement;
this.factions = params.factions; this.factions = params.factions;
if (params.isSpecial) { if (params.isSpecial) {
@ -509,6 +564,25 @@ export class Augmentation {
this.mults.bladeburner_success_chance_mult = params.bladeburner_success_chance_mult; 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) if (params.stats === undefined)
this.stats = generateStatsDescription(this.mults, params.programs, params.startingMoney); this.stats = generateStatsDescription(this.mults, params.programs, params.startingMoney);
else this.stats = params.stats; else this.stats = params.stats;

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

@ -1,11 +1,11 @@
import { Augmentation, IConstructorParams } from "./Augmentation"; import { Augmentation, IConstructorParams } from "../Augmentation";
import { AugmentationNames } from "./data/AugmentationNames"; import { AugmentationNames } from "./AugmentationNames";
import { Player } from "../Player"; import { Player } from "../../Player";
import { Programs } from "../Programs/Programs"; import { Programs } from "../../Programs/Programs";
import { WHRNG } from "../Casino/RNG"; import { WHRNG } from "../../Casino/RNG";
import React from "react"; import React from "react";
import { FactionNames } from "../Faction/data/FactionNames"; import { FactionNames } from "../../Faction/data/FactionNames";
import { CONSTANTS } from "../Constants"; import { CONSTANTS } from "../../Constants";
function getRandomBonus(): any { function getRandomBonus(): any {
const bonuses = [ const bonuses = [
@ -95,6 +95,108 @@ function getRandomBonus(): any {
return bonuses[Math.floor(bonuses.length * randomNumber.random())]; 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.
</>
),
isSpecial: true,
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.</>
),
isSpecial: true,
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 '[' ']'.</>,
isSpecial: true,
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.</>,
isSpecial: true,
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.</>,
isSpecial: true,
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.</>,
isSpecial: true,
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.</>,
isSpecial: true,
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.
</>
),
isSpecial: true,
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.</>,
isSpecial: true,
factions: [FactionNames.ShadowsOfAnarchy],
}),
];
export const initGeneralAugmentations = (): Augmentation[] => [ export const initGeneralAugmentations = (): Augmentation[] => [
new Augmentation({ new Augmentation({
name: AugmentationNames.HemoRecirculator, name: AugmentationNames.HemoRecirculator,
@ -1161,6 +1263,7 @@ export const initGeneralAugmentations = (): Augmentation[] => [
moneyCost: 0, moneyCost: 0,
info: "It's time to leave the cave.", info: "It's time to leave the cave.",
stats: null, stats: null,
isSpecial: true,
factions: [FactionNames.Daedalus], factions: [FactionNames.Daedalus],
}), }),
new Augmentation({ new Augmentation({
@ -1910,6 +2013,7 @@ export function initNeuroFluxGovernor(): Augmentation {
multiplicatively. multiplicatively.
</> </>
), ),
isSpecial: true,
hacking_chance_mult: 1.01 + donationBonus, hacking_chance_mult: 1.01 + donationBonus,
hacking_speed_mult: 1.01 + donationBonus, hacking_speed_mult: 1.01 + donationBonus,
hacking_money_mult: 1.01 + donationBonus, hacking_money_mult: 1.01 + donationBonus,
@ -1936,7 +2040,12 @@ export function initNeuroFluxGovernor(): Augmentation {
hacknet_node_core_cost_mult: 1 / (1.01 + donationBonus), hacknet_node_core_cost_mult: 1 / (1.01 + donationBonus),
hacknet_node_level_cost_mult: 1 / (1.01 + donationBonus), hacknet_node_level_cost_mult: 1 / (1.01 + donationBonus),
work_money_mult: 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", StaneksGift2 = "Stanek's Gift - Awakening",
StaneksGift3 = "Stanek's Gift - Serenity", 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 //Wasteland Augs
//PepBoy: "P.E.P-Boy", Plasma Energy Projection System //PepBoy: "P.E.P-Boy", Plasma Energy Projection System
//PepBoyForceField Generates plasma force fields //PepBoyForceField Generates plasma force fields

@ -163,7 +163,9 @@ export function BitverseRoot(props: IProps): React.ReactElement {
return lvl; return lvl;
} }
const max = n === 12 ? Infinity : 3; 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) { if (Settings.DisableASCIIArt) {
@ -171,7 +173,6 @@ export function BitverseRoot(props: IProps): React.ReactElement {
<> <>
{Object.values(BitNodes) {Object.values(BitNodes)
.filter((node) => { .filter((node) => {
console.log(node.desc);
return node.desc !== "COMING SOON"; return node.desc !== "COMING SOON";
}) })
.map((node) => { .map((node) => {

@ -164,9 +164,9 @@ export function Roulette(props: IProps): React.ReactElement {
let playerWin = strategy.match(n); let playerWin = strategy.match(n);
// oh yeah, the house straight up cheats. Try finding the seed now! // oh yeah, the house straight up cheats. Try finding the seed now!
if (playerWin && Math.random() > 0.9) { if (playerWin && Math.random() > 0.9) {
playerWin = false; while (playerWin) {
while (strategy.match(n)) { n = Math.floor(rng.random() * 37);
n = (n + 1) % 36; playerWin = strategy.match(n);
} }
} }
if (playerWin) { if (playerWin) {

@ -112,6 +112,8 @@ export const CONSTANTS: {
CodingContractBaseMoneyGain: number; CodingContractBaseMoneyGain: number;
AugmentationGraftingCostMult: number; AugmentationGraftingCostMult: number;
AugmentationGraftingTimeBase: number; AugmentationGraftingTimeBase: number;
SoACostMult: number;
SoARepMult: number;
EntropyEffect: number; EntropyEffect: number;
TotalNumBitNodes: number; TotalNumBitNodes: number;
Donations: number; // number of blood/plasma/palette donation the dev have verified., boosts NFG Donations: number; // number of blood/plasma/palette donation the dev have verified., boosts NFG
@ -281,13 +283,17 @@ export const CONSTANTS: {
AugmentationGraftingCostMult: 3, AugmentationGraftingCostMult: 3,
AugmentationGraftingTimeBase: 3600000, AugmentationGraftingTimeBase: 3600000,
// SoA mults
SoACostMult: 7,
SoARepMult: 1.3,
// Value raised to the number of entropy stacks, then multiplied to player multipliers // Value raised to the number of entropy stacks, then multiplied to player multipliers
EntropyEffect: 0.98, EntropyEffect: 0.98,
// BitNode/Source-File related stuff // BitNode/Source-File related stuff
TotalNumBitNodes: 24, TotalNumBitNodes: 24,
Donations: 4, Donations: 6,
LatestUpdate: ` LatestUpdate: `
v1.6.3 - 2022-04-01 Few stanek fixes v1.6.3 - 2022-04-01 Few stanek fixes

@ -3,7 +3,6 @@ import { Augmentation } from "../Augmentation/Augmentation";
import { PlayerOwnedAugmentation } from "../Augmentation/PlayerOwnedAugmentation"; import { PlayerOwnedAugmentation } from "../Augmentation/PlayerOwnedAugmentation";
import { AugmentationNames } from "../Augmentation/data/AugmentationNames"; import { AugmentationNames } from "../Augmentation/data/AugmentationNames";
import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers"; import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers";
import { CONSTANTS } from "../Constants";
import { Faction } from "./Faction"; import { Faction } from "./Faction";
import { Factions } from "./Factions"; import { Factions } from "./Factions";
@ -19,6 +18,7 @@ import {
import { dialogBoxCreate } from "../ui/React/DialogBox"; import { dialogBoxCreate } from "../ui/React/DialogBox";
import { InvitationEvent } from "./ui/InvitationModal"; import { InvitationEvent } from "./ui/InvitationModal";
import { FactionNames } from "./data/FactionNames"; import { FactionNames } from "./data/FactionNames";
import { updateAugmentationCosts, getNextNeuroFluxLevel } from "../Augmentation/AugmentationHelpers";
import { SFC32RNG } from "../Casino/RNG"; import { SFC32RNG } from "../Casino/RNG";
export function inviteToFaction(faction: Faction): void { 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) { } else if (aug.baseCost === 0 || Player.money >= aug.baseCost) {
const queuedAugmentation = new PlayerOwnedAugmentation(aug.name); const queuedAugmentation = new PlayerOwnedAugmentation(aug.name);
if (aug.name == AugmentationNames.NeuroFluxGovernor) { if (aug.name == AugmentationNames.NeuroFluxGovernor) {
queuedAugmentation.level = getNextNeurofluxLevel(); queuedAugmentation.level = getNextNeuroFluxLevel();
} }
Player.queuedAugmentations.push(queuedAugmentation); Player.queuedAugmentations.push(queuedAugmentation);
Player.loseMoney(aug.baseCost, "augmentations"); Player.loseMoney(aug.baseCost, "augmentations");
// If you just purchased Neuroflux Governor, recalculate the cost updateAugmentationCosts();
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)];
}
}
if (sing) { if (sing) {
return "You purchased " + aug.name; return "You purchased " + aug.name;
@ -152,24 +134,6 @@ export function purchaseAugmentation(aug: Augmentation, fac: Faction, sing = fal
return ""; 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 { export function processPassiveFactionRepGain(numCycles: number): void {
for (const name of Object.keys(Factions)) { for (const name of Object.keys(Factions)) {
if (name === Player.currentWorkFactionName) continue; if (name === Player.currentWorkFactionName) continue;
@ -201,13 +165,11 @@ export const getFactionAugmentationsFiltered = (player: IPlayer, faction: Factio
let augs = Object.values(Augmentations); let augs = Object.values(Augmentations);
// Remove special augs // Remove special augs
augs = augs.filter((a) => !a.isSpecial); augs = augs.filter((a) => !a.isSpecial || a.name != AugmentationNames.CongruityImplant);
const blacklist: string[] = [AugmentationNames.NeuroFluxGovernor, AugmentationNames.CongruityImplant]; if (player.bitNodeN === 2) {
if (player.bitNodeN !== 2) {
// TRP is not available outside of BN2 for Gangs // TRP is not available outside of BN2 for Gangs
blacklist.push(AugmentationNames.TheRedPill); augs.push(Augmentations[AugmentationNames.TheRedPill]);
} }
const rng = SFC32RNG(`BN${player.bitNodeN}.${player.sourceFileLvl(player.bitNodeN)}`); const rng = SFC32RNG(`BN${player.bitNodeN}.${player.sourceFileLvl(player.bitNodeN)}`);
@ -226,9 +188,6 @@ export const getFactionAugmentationsFiltered = (player: IPlayer, faction: Factio
}; };
augs = augs.filter(uniqueFilter); augs = augs.filter(uniqueFilter);
// Remove blacklisted augs
augs = augs.filter((a) => !blacklist.includes(a.name));
return augs.map((a) => a.name); return augs.map((a) => a.name);
} }

@ -3,6 +3,7 @@ import { IMap } from "../types";
import { FactionNames } from "./data/FactionNames"; import { FactionNames } from "./data/FactionNames";
import { use } from "../ui/Context"; import { use } from "../ui/Context";
import { Option } from "./ui/Option"; import { Option } from "./ui/Option";
import { Typography } from "@mui/material";
interface FactionInfoParams { interface FactionInfoParams {
infoText?: JSX.Element; 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", CyberSec = "CyberSec",
Bladeburners = "Bladeburners", Bladeburners = "Bladeburners",
ChurchOfTheMachineGod = "Church of the Machine God", 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 TableBody from "@mui/material/TableBody";
import Table from "@mui/material/Table"; import Table from "@mui/material/Table";
import { getGenericAugmentationPriceMultiplier } from "../../Augmentation/AugmentationHelpers"; import { getGenericAugmentationPriceMultiplier } from "../../Augmentation/AugmentationHelpers";
import { FactionNames } from "../data/FactionNames";
type IProps = { type IProps = {
faction: Faction; 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 ( return (
<> <>
@ -185,9 +194,7 @@ export function AugmentationsPage(props: IProps): React.ReactElement {
</Typography> </Typography>
} }
> >
<Typography> {multiplierComponent}
Price multiplier: x {numeralWrapper.formatMultiplier(getGenericAugmentationPriceMultiplier())}
</Typography>
</Tooltip> </Tooltip>
</Box> </Box>
<Button onClick={() => switchSortOrder(PurchaseAugmentationsOrderSetting.Cost)}>Sort by Cost</Button> <Button onClick={() => switchSortOrder(PurchaseAugmentationsOrderSetting.Cost)}>Sort by Cost</Button>

@ -18,11 +18,11 @@ import { Faction } from "../Faction";
import { use } from "../../ui/Context"; import { use } from "../../ui/Context";
import { CreateGangModal } from "./CreateGangModal"; import { CreateGangModal } from "./CreateGangModal";
import Typography from "@mui/material/Typography"; import { Box, Paper, Typography, Button, Tooltip } from "@mui/material";
import Button from "@mui/material/Button";
import { CovenantPurchasesRoot } from "../../PersonObjects/Sleeve/ui/CovenantPurchasesRoot"; import { CovenantPurchasesRoot } from "../../PersonObjects/Sleeve/ui/CovenantPurchasesRoot";
import { FactionNames } from "../data/FactionNames"; import { FactionNames } from "../data/FactionNames";
import { GangConstants } from "../../Gang/data/Constants"; import { GangConstants } from "../../Gang/data/Constants";
import { GangButton } from "./GangButton";
type IProps = { type IProps = {
faction: Faction; faction: Faction;
@ -62,18 +62,8 @@ function MainPage({ faction, rerender, onAugmentations }: IMainProps): React.Rea
const player = use.Player(); const player = use.Player();
const router = use.Router(); const router = use.Router();
const [sleevesOpen, setSleevesOpen] = useState(false); const [sleevesOpen, setSleevesOpen] = useState(false);
const [gangOpen, setGangOpen] = useState(false);
const factionInfo = faction.getInfo(); 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 { function startWork(): void {
player.startFocusing(); player.startFocusing();
router.toWork(); router.toWork();
@ -105,15 +95,6 @@ function MainPage({ faction, rerender, onAugmentations }: IMainProps): React.Rea
const canPurchaseSleeves = faction.name === FactionNames.TheCovenant && player.bitNodeN === 10; 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 ( return (
<> <>
<Button onClick={() => router.toFactions()}>Back</Button> <Button onClick={() => router.toFactions()}>Back</Button>
@ -121,12 +102,7 @@ function MainPage({ faction, rerender, onAugmentations }: IMainProps): React.Rea
{faction.name} {faction.name}
</Typography> </Typography>
<Info faction={faction} factionInfo={factionInfo} /> <Info faction={faction} factionInfo={factionInfo} />
{canAccessGang && ( <GangButton faction={faction} />
<>
<Option buttonText={"Manage Gang"} infoText={gangInfo} onClick={manageGang} />
<CreateGangModal facName={faction.name} open={gangOpen} onClose={() => setGangOpen(false)} />
</>
)}
{!isPlayersGang && factionInfo.offerHackingWork && ( {!isPlayersGang && factionInfo.offerHackingWork && (
<Option <Option
buttonText={"Hacking Contracts"} 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 Assignment = props.factionInfo.assignment ?? DefaultAssignment;
const favorGain = props.faction.getFavorGain(); const favorGain = props.faction.getFavorGain();
return ( return (
<> <>
<Typography classes={{ root: classes.noformat }}>{props.factionInfo.infoText}</Typography> <Typography classes={{ root: classes.noformat }}>{props.factionInfo.infoText}</Typography>

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

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

@ -42,6 +42,7 @@ export const HashUpgradesMetadata: IConstructorParams[] = [
costPerLevel: 50, costPerLevel: 50,
desc: desc:
"Use hashes to increase the maximum amount of money on a single server by 2%. " + "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 " + "This effect persists until you install Augmentations (since servers " +
"are reset at that time).", "are reset at that time).",
hasTargetServer: true, 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, 1.1) *
Math.pow(difficulty, 1.2) *
30 *
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 { BlinkingCursor } from "./BlinkingCursor";
import Typography from "@mui/material/Typography"; import Typography from "@mui/material/Typography";
import { KEY } from "../../utils/helpers/keyCodes"; import { KEY } from "../../utils/helpers/keyCodes";
import { Player } from "../../Player";
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
interface Difficulty { interface Difficulty {
[key: string]: number; [key: string]: number;
@ -34,6 +36,7 @@ export function BackwardGame(props: IMinigameProps): React.ReactElement {
const timer = difficulty.timer; const timer = difficulty.timer;
const [answer] = useState(makeAnswer(difficulty)); const [answer] = useState(makeAnswer(difficulty));
const [guess, setGuess] = useState(""); const [guess, setGuess] = useState("");
const hasAugment = Player.hasAugmentation(AugmentationNames.ChaosOfDionysus, true);
function press(this: Document, event: KeyboardEvent): void { function press(this: Document, event: KeyboardEvent): void {
event.preventDefault(); event.preventDefault();
@ -48,11 +51,13 @@ export function BackwardGame(props: IMinigameProps): React.ReactElement {
<Grid container spacing={3}> <Grid container spacing={3}>
<GameTimer millis={timer} onExpire={props.onFailure} /> <GameTimer millis={timer} onExpire={props.onFailure} />
<Grid item xs={12}> <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} /> <KeyHandler onKeyDown={press} onFailure={props.onFailure} />
</Grid> </Grid>
<Grid item xs={6}> <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>
<Grid item xs={6}> <Grid item xs={6}>
<Typography> <Typography>

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

@ -1,4 +1,4 @@
import React, { useState } from "react"; import React, { useEffect, useState } from "react";
import Grid from "@mui/material/Grid"; import Grid from "@mui/material/Grid";
import { IMinigameProps } from "./IMinigameProps"; import { IMinigameProps } from "./IMinigameProps";
import { KeyHandler } from "./KeyHandler"; import { KeyHandler } from "./KeyHandler";
@ -6,6 +6,9 @@ import { GameTimer } from "./GameTimer";
import { interpolate } from "./Difficulty"; import { interpolate } from "./Difficulty";
import Typography from "@mui/material/Typography"; import Typography from "@mui/material/Typography";
import { KEY } from "../../utils/helpers/keyCodes"; 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"; import { downArrowSymbol, upArrowSymbol } from "../utils";
interface Difficulty { interface Difficulty {
@ -31,20 +34,54 @@ export function BribeGame(props: IMinigameProps): React.ReactElement {
interpolate(difficulties, props.difficulty, difficulty); interpolate(difficulties, props.difficulty, difficulty);
const timer = difficulty.timer; const timer = difficulty.timer;
const [choices] = useState(makeChoices(difficulty)); const [choices] = useState(makeChoices(difficulty));
const [correctIndex, setCorrectIndex] = useState(0);
const [index, setIndex] = 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 { function press(this: Document, event: KeyboardEvent): void {
event.preventDefault(); event.preventDefault();
const k = event.key; const k = event.key;
if (k === KEY.SPACE) { if (k === KEY.SPACE) {
if (positive.includes(choices[index])) props.onSuccess(); if (positive.includes(currentChoice)) props.onSuccess();
else props.onFailure(); else props.onFailure();
return; return;
} }
let newIndex = index; let newIndex = index;
if ([KEY.UP_ARROW, KEY.W, KEY.RIGHT_ARROW, KEY.D].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((key) => key 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 < 0) newIndex += choices.length;
while (newIndex > choices.length - 1) newIndex -= choices.length; while (newIndex > choices.length - 1) newIndex -= choices.length;
setIndex(newIndex); setIndex(newIndex);
@ -58,13 +95,13 @@ export function BribeGame(props: IMinigameProps): React.ReactElement {
<KeyHandler onKeyDown={press} onFailure={props.onFailure} /> <KeyHandler onKeyDown={press} onFailure={props.onFailure} />
</Grid> </Grid>
<Grid item xs={6}> <Grid item xs={6}>
<Typography variant="h5" color="primary"> <Typography variant="h5" color={upColor}>
{upArrowSymbol} {upArrowSymbol}
</Typography> </Typography>
<Typography variant="h5" color="primary"> <Typography variant="h5" color={choiceColor}>
{choices[index]} {currentChoice}
</Typography> </Typography>
<Typography variant="h5" color="primary"> <Typography variant="h5" color={downColor}>
{downArrowSymbol} {downArrowSymbol}
</Typography> </Typography>
</Grid> </Grid>

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

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

@ -13,6 +13,7 @@ import { MinesweeperGame } from "./MinesweeperGame";
import { WireCuttingGame } from "./WireCuttingGame"; import { WireCuttingGame } from "./WireCuttingGame";
import { Victory } from "./Victory"; import { Victory } from "./Victory";
import Typography from "@mui/material/Typography"; import Typography from "@mui/material/Typography";
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
interface IProps { interface IProps {
StartingDifficulty: number; StartingDifficulty: number;
@ -91,7 +92,9 @@ export function Game(props: IProps): React.ReactElement {
pushResult(false); pushResult(false);
// Kill the player immediately if they use automation, so // Kill the player immediately if they use automation, so
// it's clear they're not meant to // 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)) { if (player.takeDamage(damage)) {
router.toCity(); router.toCity();
return; return;

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

@ -1,48 +1,22 @@
import { IPlayer } from "../../PersonObjects/IPlayer";
import React, { useState } from "react"; import React, { useState } from "react";
import { Intro } from "./Intro"; import { Intro } from "./Intro";
import { Game } from "./Game"; import { Game } from "./Game";
import { Location } from "../../Locations/Location"; import { Location } from "../../Locations/Location";
import { use } from "../../ui/Context"; import { use } from "../../ui/Context";
import { calculateSkill } from "../../PersonObjects/formulas/skill"; import { calculateDifficulty, calculateReward } from "../formulas/game";
interface IProps { interface IProps {
location: Location; 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 { export function InfiltrationRoot(props: IProps): React.ReactElement {
const player = use.Player(); const player = use.Player();
const router = use.Router(); const router = use.Router();
const [start, setStart] = useState(false); const [start, setStart] = useState(false);
if (props.location.infiltrationData === undefined) throw new Error("Trying to do infiltration on invalid location."); if (props.location.infiltrationData === undefined) throw new Error("Trying to do infiltration on invalid location.");
const startingDifficulty = props.location.infiltrationData.startingSecurityLevel; const startingSecurityLevel = props.location.infiltrationData.startingSecurityLevel;
const difficulty = calcDifficulty(player, startingDifficulty); const difficulty = calculateDifficulty(player, startingSecurityLevel);
const reward = calcReward(player, startingDifficulty); const reward = calculateReward(player, startingSecurityLevel);
console.log(`${difficulty} ${reward}`);
function cancel(): void { function cancel(): void {
router.toCity(); router.toCity();
@ -62,7 +36,7 @@ export function InfiltrationRoot(props: IProps): React.ReactElement {
return ( return (
<Game <Game
StartingDifficulty={startingDifficulty} StartingDifficulty={startingSecurityLevel}
Difficulty={difficulty} Difficulty={difficulty}
Reward={reward} Reward={reward}
MaxLevel={props.location.infiltrationData.maxClearanceLevel} MaxLevel={props.location.infiltrationData.maxClearanceLevel}

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

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

@ -3,12 +3,19 @@ import React, { useState } from "react";
import Grid from "@mui/material/Grid"; import Grid from "@mui/material/Grid";
import { Money } from "../../ui/React/Money"; import { Money } from "../../ui/React/Money";
import { Reputation } from "../../ui/React/Reputation"; import { Reputation } from "../../ui/React/Reputation";
import { BitNodeMultipliers } from "../../BitNode/BitNodeMultipliers";
import { use } from "../../ui/Context"; import { use } from "../../ui/Context";
import Typography from "@mui/material/Typography"; import Typography from "@mui/material/Typography";
import Button from "@mui/material/Button"; import Button from "@mui/material/Button";
import MenuItem from "@mui/material/MenuItem"; import MenuItem from "@mui/material/MenuItem";
import Select, { SelectChangeEvent } from "@mui/material/Select"; 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 { interface IProps {
StartingDifficulty: number; StartingDifficulty: number;
@ -23,31 +30,25 @@ export function Victory(props: IProps): React.ReactElement {
const [faction, setFaction] = useState("none"); const [faction, setFaction] = useState("none");
function quitInfiltration(): void { function quitInfiltration(): void {
handleInfiltrators();
router.toCity(); 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 = const isMemberOfInfiltrators = player.factions.includes(FactionNames.ShadowsOfAnarchy);
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;
function sell(): void { function sell(): void {
handleInfiltrators();
player.gainMoney(moneyGain, "infiltration"); player.gainMoney(moneyGain, "infiltration");
quitInfiltration(); quitInfiltration();
} }
function trade(): void { function trade(): void {
handleInfiltrators();
if (faction === "none") return; if (faction === "none") return;
Factions[faction].playerReputation += repGain; Factions[faction].playerReputation += repGain;
quitInfiltration(); quitInfiltration();
@ -57,6 +58,13 @@ export function Victory(props: IProps): React.ReactElement {
setFaction(event.target.value); setFaction(event.target.value);
} }
function handleInfiltrators(): void {
inviteToFaction(Factions[FactionNames.ShadowsOfAnarchy]);
if (isMemberOfInfiltrators) {
soa.playerReputation += infiltrationRepGain;
}
}
return ( return (
<> <>
<Grid container spacing={3}> <Grid container spacing={3}>
@ -65,7 +73,15 @@ export function Victory(props: IProps): React.ReactElement {
</Grid> </Grid>
<Grid item xs={10}> <Grid item xs={10}>
<Typography variant="h5" color="primary"> <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> </Typography>
<Select value={faction} onChange={changeDropdown}> <Select value={faction} onChange={changeDropdown}>
<MenuItem key={"none"} value={"none"}> <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 Grid from "@mui/material/Grid";
import Typography from "@mui/material/Typography"; import Typography from "@mui/material/Typography";
import { IMinigameProps } from "./IMinigameProps"; import { IMinigameProps } from "./IMinigameProps";
@ -7,6 +7,9 @@ import { GameTimer } from "./GameTimer";
import { random } from "../utils"; import { random } from "../utils";
import { interpolate } from "./Difficulty"; import { interpolate } from "./Difficulty";
import { KEY } from "../../utils/helpers/keyCodes"; import { KEY } from "../../utils/helpers/keyCodes";
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
import { Player } from "../../Player";
import { Settings } from "../../Settings/Settings";
interface Difficulty { interface Difficulty {
[key: string]: number; [key: string]: number;
@ -61,11 +64,27 @@ export function WireCuttingGame(props: IMinigameProps): React.ReactElement {
const [wires] = useState(generateWires(difficulty)); const [wires] = useState(generateWires(difficulty));
const [cutWires, setCutWires] = useState(new Array(wires.length).fill(false)); const [cutWires, setCutWires] = useState(new Array(wires.length).fill(false));
const [questions] = useState(generateQuestion(wires, difficulty)); const [questions] = useState(generateQuestion(wires, difficulty));
const hasAugment = Player.hasAugmentation(AugmentationNames.KnowledgeOfApollo, true);
function checkWire(wireNum: number): boolean { function checkWire(wireNum: number): boolean {
return !questions.some((q) => q.shouldCut(wires[wireNum - 1], wireNum - 1)); 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++) {
let shouldBeCut = false;
for (let i = 0; i < questions.length; i++) {
shouldBeCut = shouldBeCut || questions[i].shouldCut(wires[j], j);
}
wiresToBeCut.push(shouldBeCut);
}
if (wiresToBeCut.every((b, i) => b === cutWires[i])) {
props.onSuccess();
}
}, [cutWires]);
function press(this: Document, event: KeyboardEvent): void { function press(this: Document, event: KeyboardEvent): void {
event.preventDefault(); event.preventDefault();
const wireNum = parseInt(event.key); const wireNum = parseInt(event.key);
@ -74,23 +93,10 @@ export function WireCuttingGame(props: IMinigameProps): React.ReactElement {
setCutWires((old) => { setCutWires((old) => {
const next = [...old]; const next = [...old];
next[wireNum - 1] = true; next[wireNum - 1] = true;
if (checkWire(wireNum)) { if (!checkWire(wireNum)) {
props.onFailure(); props.onFailure();
} }
// check if we won
const wiresToBeCut = [];
for (let j = 0; j < wires.length; j++) {
let shouldBeCut = false;
for (let i = 0; i < questions.length; i++) {
shouldBeCut = shouldBeCut || questions[i].shouldCut(wires[j], j);
}
wiresToBeCut.push(shouldBeCut);
}
if (wiresToBeCut.every((b, i) => b === next[i])) {
props.onSuccess();
}
return next; return next;
}); });
} }
@ -104,18 +110,28 @@ export function WireCuttingGame(props: IMinigameProps): React.ReactElement {
<Typography key={i}>{question.toString()}</Typography> <Typography key={i}>{question.toString()}</Typography>
))} ))}
<Typography> <Typography>
{new Array(wires.length).fill(0).map((_, i) => ( {new Array(wires.length).fill(0).map((_, i) => {
<span key={i}>&nbsp;{i + 1}&nbsp;&nbsp;&nbsp;&nbsp;</span> 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> </Typography>
{new Array(8).fill(0).map((_, i) => ( {new Array(8).fill(0).map((_, i) => (
<div key={i}> <div key={i}>
<Typography> <Typography>
{wires.map((wire, j) => { {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>; 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 ( return (
<span key={j} style={{ color: wire.colors[i % wire.colors.length] }}> <span key={j} style={{ color: wireColor }}>
|{wire.tpe}|&nbsp;&nbsp;&nbsp; |{wire.tpe}|&nbsp;&nbsp;&nbsp;
</span> </span>
); );

@ -70,6 +70,10 @@ export const RamCostConstants: IMap<number> = {
ScriptStanekPlace: 5, ScriptStanekPlace: 5,
ScriptStanekFragmentAt: 2, ScriptStanekFragmentAt: 2,
ScriptStanekDeleteAt: 0.15, ScriptStanekDeleteAt: 0.15,
ScriptInfiltrationCalculateDifficulty: 2.5,
ScriptInfiltrationCalculateRewards: 2.5,
ScriptInfiltrationGetLocations: 5,
ScriptInfiltrationGetInfiltrations: 15,
ScriptStanekAcceptGift: 2, ScriptStanekAcceptGift: 2,
}; };
@ -245,6 +249,13 @@ const bladeburner: IMap<any> = {
getBonusTime: 0, getBonusTime: 0,
}; };
const infiltration: IMap<any> = {
calculateDifficulty: RamCostConstants.ScriptInfiltrationCalculateDifficulty,
calculateRewards: RamCostConstants.ScriptInfiltrationCalculateRewards,
calculateGetLocations: RamCostConstants.ScriptInfiltrationGetLocations,
calculateGetInfiltrations: RamCostConstants.ScriptInfiltrationGetInfiltrations,
};
// Coding Contract API // Coding Contract API
const codingcontract: IMap<any> = { const codingcontract: IMap<any> = {
attempt: RamCostConstants.ScriptCodingContractBaseRamCost, attempt: RamCostConstants.ScriptCodingContractBaseRamCost,
@ -314,6 +325,7 @@ export const RamCosts: IMap<any> = {
...singularity, // singularity is in namespace & toplevel ...singularity, // singularity is in namespace & toplevel
gang, gang,
bladeburner, bladeburner,
infiltration,
codingcontract, codingcontract,
sleeve, sleeve,
stanek, stanek,

@ -65,6 +65,7 @@ import { NetscriptSleeve } from "./NetscriptFunctions/Sleeve";
import { NetscriptExtra } from "./NetscriptFunctions/Extra"; import { NetscriptExtra } from "./NetscriptFunctions/Extra";
import { NetscriptHacknet } from "./NetscriptFunctions/Hacknet"; import { NetscriptHacknet } from "./NetscriptFunctions/Hacknet";
import { NetscriptStanek } from "./NetscriptFunctions/Stanek"; import { NetscriptStanek } from "./NetscriptFunctions/Stanek";
import { NetscriptInfiltration } from "./NetscriptFunctions/Infiltration";
import { NetscriptUserInterface } from "./NetscriptFunctions/UserInterface"; import { NetscriptUserInterface } from "./NetscriptFunctions/UserInterface";
import { NetscriptBladeburner } from "./NetscriptFunctions/Bladeburner"; import { NetscriptBladeburner } from "./NetscriptFunctions/Bladeburner";
import { NetscriptCodingContract } from "./NetscriptFunctions/CodingContract"; import { NetscriptCodingContract } from "./NetscriptFunctions/CodingContract";
@ -81,6 +82,7 @@ import {
Gang as IGang, Gang as IGang,
Bladeburner as IBladeburner, Bladeburner as IBladeburner,
Stanek as IStanek, Stanek as IStanek,
Infiltration as IInfiltration,
RunningScript as IRunningScript, RunningScript as IRunningScript,
RecentScript as IRecentScript, RecentScript as IRecentScript,
SourceFileLvl, SourceFileLvl,
@ -113,6 +115,7 @@ interface NS extends INS {
gang: IGang; gang: IGang;
bladeburner: IBladeburner; bladeburner: IBladeburner;
stanek: IStanek; stanek: IStanek;
infiltration: IInfiltration;
} }
export function NetscriptFunctions(workerScript: WorkerScript): NS { export function NetscriptFunctions(workerScript: WorkerScript): NS {
@ -525,6 +528,8 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
const sleeve = NetscriptSleeve(Player, workerScript, helper); const sleeve = NetscriptSleeve(Player, workerScript, helper);
const extra = NetscriptExtra(Player, workerScript, helper); const extra = NetscriptExtra(Player, workerScript, helper);
const hacknet = NetscriptHacknet(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") const stanek = wrapAPI(helper, {}, workerScript, NetscriptStanek(Player, workerScript, helper), "stanek")
.stanek as unknown as IStanek; .stanek as unknown as IStanek;
const bladeburner = NetscriptBladeburner(Player, workerScript, helper); const bladeburner = NetscriptBladeburner(Player, workerScript, helper);
@ -547,6 +552,7 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
sleeve: sleeve, sleeve: sleeve,
corporation: corporation, corporation: corporation,
stanek: stanek, stanek: stanek,
infiltration: infiltration,
ui: ui, ui: ui,
formulas: formulas, formulas: formulas,
stock: stockmarket, stock: stockmarket,

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

@ -202,7 +202,7 @@ function _getScriptUrls(script: Script, scripts: Script[], seen: Script[]): Scri
// We automatically define a print function() in the NetscriptJS module so that // We automatically define a print function() in the NetscriptJS module so that
// accidental calls to window.print() do not bring up the "print screen" dialog // accidental calls to window.print() do not bring up the "print screen" dialog
transformedCode += `\n\nfunction print() {throw new Error("Invalid call to window.print(). Did you mean to use Netscript's print()?");}\n//# sourceURL=${script.server}/${script.filename}`; transformedCode += `\n//# sourceURL=${script.server}/${script.filename}`;
const blob = URL.createObjectURL(makeScriptBlob(transformedCode)); const blob = URL.createObjectURL(makeScriptBlob(transformedCode));
// Push the blob URL onto the top of the stack. // Push the blob URL onto the top of the stack.

@ -1,15 +1,23 @@
import { Augmentations } from "../../Augmentation/Augmentations"; import { Augmentations } from "../../Augmentation/Augmentations";
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames"; import { GraftableAugmentation } from "./GraftableAugmentation";
import { IPlayer } from "../IPlayer"; import { IPlayer } from "../IPlayer";
export const getGraftingAvailableAugs = (player: IPlayer): string[] => { export const getGraftingAvailableAugs = (player: IPlayer): string[] => {
const augs: string[] = []; const augs: string[] = [];
for (const [augName, aug] of Object.entries(Augmentations)) { for (const [augName, aug] of Object.entries(Augmentations)) {
if (augName === AugmentationNames.NeuroFluxGovernor || augName === AugmentationNames.TheRedPill || aug.isSpecial) if (aug.isSpecial) continue;
continue;
augs.push(augName); augs.push(augName);
} }
return augs.filter((augmentation: string) => !player.hasAugmentation(augmentation)); 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 { Construction, CheckBox, CheckBoxOutlineBlank } from "@mui/icons-material";
import { Box, Button, Container, List, ListItemButton, Paper, Typography } from "@mui/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 { Augmentation } from "../../../Augmentation/Augmentation";
import { Augmentations } from "../../../Augmentation/Augmentations"; import { Augmentations } from "../../../Augmentation/Augmentations";
import { AugmentationNames } from "../../../Augmentation/data/AugmentationNames"; import { AugmentationNames } from "../../../Augmentation/data/AugmentationNames";
@ -15,7 +15,7 @@ import { ConfirmationModal } from "../../../ui/React/ConfirmationModal";
import { Money } from "../../../ui/React/Money"; import { Money } from "../../../ui/React/Money";
import { convertTimeMsToTimeElapsedString, formatNumber } from "../../../utils/StringHelperFunctions"; import { convertTimeMsToTimeElapsedString, formatNumber } from "../../../utils/StringHelperFunctions";
import { IPlayer } from "../../IPlayer"; import { IPlayer } from "../../IPlayer";
import { getGraftingAvailableAugs } from "../GraftingHelpers"; import { getGraftingAvailableAugs, calculateGraftingTimeWithBonus } from "../GraftingHelpers";
import { GraftableAugmentation } from "../GraftableAugmentation"; import { GraftableAugmentation } from "../GraftableAugmentation";
const GraftableAugmentations: IMap<GraftableAugmentation> = {}; const GraftableAugmentations: IMap<GraftableAugmentation> = {};
@ -63,6 +63,16 @@ export const GraftingRoot = (): React.ReactElement => {
const [selectedAug, setSelectedAug] = useState(getGraftingAvailableAugs(player)[0]); const [selectedAug, setSelectedAug] = useState(getGraftingAvailableAugs(player)[0]);
const [graftOpen, setGraftOpen] = useState(false); 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 ( return (
<Container disableGutters maxWidth="lg" sx={{ mx: 0 }}> <Container disableGutters maxWidth="lg" sx={{ mx: 0 }}>
<Button onClick={() => router.toLocation(Locations[LocationName.NewTokyoVitaLife])}>Back</Button> <Button onClick={() => router.toLocation(Locations[LocationName.NewTokyoVitaLife])}>Back</Button>
@ -132,7 +142,7 @@ export const GraftingRoot = (): React.ReactElement => {
<Typography color={Settings.theme.info}> <Typography color={Settings.theme.info}>
<b>Time to Graft:</b>{" "} <b>Time to Graft:</b>{" "}
{convertTimeMsToTimeElapsedString( {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 */} {/* Use formula so the displayed creation time is accurate to player bonus */}
</Typography> </Typography>

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

@ -97,6 +97,13 @@ export abstract class Person {
bladeburner_analysis_mult = 1; bladeburner_analysis_mult = 1;
bladeburner_success_chance_mult = 1; bladeburner_success_chance_mult = 1;
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 * Augmentations
*/ */
@ -195,6 +202,24 @@ export abstract class Person {
this.crime_success_mult = 1; this.crime_success_mult = 1;
this.work_money_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;
} }
/** /**

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

@ -7,7 +7,7 @@ import { Augmentation } from "../../Augmentation/Augmentation";
import { calculateEntropy } from "../Grafting/EntropyAccumulation"; 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; const augName: string = aug instanceof Augmentation ? aug.name : aug;
for (const owned of this.augmentations) { 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) { for (const owned of this.queuedAugmentations) {
if (owned.name === augName) { if (owned.name === augName) {
return true; return true;

@ -2,9 +2,7 @@ import { Factions } from "../../Faction/Factions";
import { Faction } from "../../Faction/Faction"; import { Faction } from "../../Faction/Faction";
import { Gang } from "../../Gang/Gang"; import { Gang } from "../../Gang/Gang";
import { IPlayer } from "../IPlayer"; 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 { export function canAccessGang(this: IPlayer): boolean {
if (this.bitNodeN === 2) { if (this.bitNodeN === 2) {
@ -14,7 +12,11 @@ export function canAccessGang(this: IPlayer): boolean {
return false; 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 { export function getGangFaction(this: IPlayer): Faction {

@ -65,6 +65,7 @@ import { SnackbarEvents, ToastVariant } from "../../ui/React/Snackbar";
import { calculateClassEarnings } from "../formulas/work"; import { calculateClassEarnings } from "../formulas/work";
import { achievements } from "../../Achievements/Achievements"; import { achievements } from "../../Achievements/Achievements";
import { FactionNames } from "../../Faction/data/FactionNames"; import { FactionNames } from "../../Faction/data/FactionNames";
import { graftingIntBonus } from "../Grafting/GraftingHelpers";
export function init(this: IPlayer): void { export function init(this: IPlayer): void {
/* Initialize Player's home computer */ /* Initialize Player's home computer */
@ -1350,7 +1351,7 @@ export function craftAugmentationWork(this: IPlayer, numCycles: number): boolean
focusBonus = this.focus ? 1 : CONSTANTS.BaseFocusBonus; focusBonus = this.focus ? 1 : CONSTANTS.BaseFocusBonus;
} }
let skillMult = 1 + (this.getIntelligenceBonus(3) - 1) / 3; let skillMult = graftingIntBonus(this);
skillMult *= focusBonus; skillMult *= focusBonus;
this.timeWorked += CONSTANTS._idleSpeed * numCycles; this.timeWorked += CONSTANTS._idleSpeed * numCycles;

@ -519,6 +519,14 @@ export class Sleeve extends Person {
break; 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; fac.playerReputation += this.getRepGain(p) * cyclesUsed;
break; break;
} }

@ -5,7 +5,6 @@ import { IPlayer } from "../IPlayer";
import { Augmentation } from "../../Augmentation/Augmentation"; import { Augmentation } from "../../Augmentation/Augmentation";
import { Augmentations } from "../../Augmentation/Augmentations"; import { Augmentations } from "../../Augmentation/Augmentations";
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
import { Faction } from "../../Faction/Faction"; import { Faction } from "../../Faction/Faction";
import { Factions } from "../../Faction/Factions"; import { Factions } from "../../Faction/Factions";
@ -22,9 +21,6 @@ export function findSleevePurchasableAugs(sleeve: Sleeve, p: IPlayer): Augmentat
// Helper function that helps filter out augs that are already owned // Helper function that helps filter out augs that are already owned
// and augs that aren't allowed for sleeves // and augs that aren't allowed for sleeves
function isAvailableForSleeve(aug: Augmentation): boolean { function isAvailableForSleeve(aug: Augmentation): boolean {
if (aug.name === AugmentationNames.NeuroFluxGovernor) {
return false;
}
if (ownedAugNames.includes(aug.name)) { if (ownedAugNames.includes(aug.name)) {
return false; return false;
} }

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

@ -24,6 +24,8 @@ import { SxProps } from "@mui/system";
import { PlayerObject } from "./PersonObjects/Player/PlayerObject"; import { PlayerObject } from "./PersonObjects/Player/PlayerObject";
import { pushGameSaved } from "./Electron"; import { pushGameSaved } from "./Electron";
import { defaultMonacoTheme } from "./ScriptEditor/ui/themes"; import { defaultMonacoTheme } from "./ScriptEditor/ui/themes";
import { FactionNames } from "./Faction/data/FactionNames";
import { Faction } from "./Faction/Faction";
/* SaveObject.js /* SaveObject.js
* Defines the object used to save/load games * Defines the object used to save/load games
@ -400,6 +402,7 @@ function evaluateVersionCompatibility(ver: string | number): void {
} }
//Fix contract names //Fix contract names
if (ver < 16) { if (ver < 16) {
Factions[FactionNames.ShadowsOfAnarchy] = new Faction(FactionNames.ShadowsOfAnarchy);
//Iterate over all contracts on all servers //Iterate over all contracts on all servers
for (const server of GetAllServers()) { for (const server of GetAllServers()) {
for (const contract of server.contracts) { for (const contract of server.contracts) {

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

@ -2198,7 +2198,7 @@ export interface Singularity {
* RAM cost: 5 GB * 16/4/1 * 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. * @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.
*/ */
@ -4273,6 +4273,41 @@ interface Stanek {
acceptGift(): boolean; 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;
}
/** /**
* User Interface API. * User Interface API.
* @public * @public
@ -4422,6 +4457,11 @@ export interface NS {
* RAM cost: 0 GB * RAM cost: 0 GB
*/ */
readonly stanek: Stanek; readonly stanek: Stanek;
/**
* Namespace for infiltration functions.
* RAM cost: 0 GB
*/
readonly infiltration: Infiltration;
/** /**
* Namespace for corporation functions. * Namespace for corporation functions.
* RAM cost: 0 GB * RAM cost: 0 GB

@ -1,6 +1,7 @@
import { getRandomInt } from "../utils/helpers/getRandomInt"; import { getRandomInt } from "../utils/helpers/getRandomInt";
import { MinHeap } from "../utils/Heap"; import { MinHeap } from "../utils/Heap";
import { comprGenChar, comprLZGenerate, comprLZEncode, comprLZDecode } from "../utils/CompressionContracts";
import { HammingEncode, HammingDecode } from "../utils/HammingCodeTools"; import { HammingEncode, HammingDecode } from "../utils/HammingCodeTools";
/* tslint:disable:completed-docs no-magic-numbers arrow-return-shorthand */ /* tslint:disable:completed-docs no-magic-numbers arrow-return-shorthand */
@ -1456,4 +1457,155 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [
else return false; 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;
},
},
]; ];

@ -4,6 +4,7 @@ import ReactDOM from "react-dom";
import { TTheme as Theme, ThemeEvents, refreshTheme } from "./Themes/ui/Theme"; import { TTheme as Theme, ThemeEvents, refreshTheme } from "./Themes/ui/Theme";
import { LoadingScreen } from "./ui/LoadingScreen"; import { LoadingScreen } from "./ui/LoadingScreen";
import { initElectron } from "./Electron"; import { initElectron } from "./Electron";
import { AlertEvents } from "./ui/React/AlertManager";
initElectron(); initElectron();
globalThis["React"] = React; globalThis["React"] = React;
globalThis["ReactDOM"] = ReactDOM; globalThis["ReactDOM"] = ReactDOM;
@ -34,3 +35,9 @@ function rerender(): void {
return "Your work will be lost."; return "Your work will be lost.";
}; };
})(); })();
(function () {
window.print = () => {
throw new Error("You accidentally called window.print instead of ns.print");
};
})();

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

@ -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(987654321)).toEqual("987.654m");
expect(numeralWrapper.formatReallyBigNumber(987654321987)).toEqual("987.654b"); expect(numeralWrapper.formatReallyBigNumber(987654321987)).toEqual("987.654b");
expect(numeralWrapper.formatReallyBigNumber(987654321987654)).toEqual("987.654t"); expect(numeralWrapper.formatReallyBigNumber(987654321987654)).toEqual("987.654t");
expect(numeralWrapper.formatReallyBigNumber(987654321987654e3)).toEqual("987.654q"); expect(numeralWrapper.formatReallyBigNumber(987654321987654000)).toEqual("987.654q");
expect(numeralWrapper.formatReallyBigNumber(987654321987654e6)).toEqual("987.654Q"); expect(numeralWrapper.formatReallyBigNumber(987654321987654000000)).toEqual("987.654Q");
expect(numeralWrapper.formatReallyBigNumber(987654321987654e9)).toEqual("987.654s"); expect(numeralWrapper.formatReallyBigNumber(987654321987654000000000)).toEqual("987.654s");
expect(numeralWrapper.formatReallyBigNumber(987654321987654e12)).toEqual("987.654S"); expect(numeralWrapper.formatReallyBigNumber(987654321987654000000000000)).toEqual("987.654S");
expect(numeralWrapper.formatReallyBigNumber(987654321987654e15)).toEqual("987.654o"); expect(numeralWrapper.formatReallyBigNumber(987654321987654000000000000000)).toEqual("987.654o");
expect(numeralWrapper.formatReallyBigNumber(987654321987654e18)).toEqual("987.654n"); expect(numeralWrapper.formatReallyBigNumber(987654321987654000000000000000000)).toEqual("987.654n");
}); });
test("should format even bigger really big numbers in scientific format", () => { test("should format even bigger really big numbers in scientific format", () => {
expect(numeralWrapper.formatReallyBigNumber(987654321987654e21)).toEqual("9.877e+35"); expect(numeralWrapper.formatReallyBigNumber(987654321987654000000000000000000000)).toEqual("9.877e+35");
expect(numeralWrapper.formatReallyBigNumber(987654321987654e22)).toEqual("9.877e+36"); expect(numeralWrapper.formatReallyBigNumber(9876543219876540000000000000000000000)).toEqual("9.877e+36");
expect(numeralWrapper.formatReallyBigNumber(987654321987654e23)).toEqual("9.877e+37"); expect(numeralWrapper.formatReallyBigNumber(98765432198765400000000000000000000000)).toEqual("9.877e+37");
}); });
test("should format percentage", () => { test("should format percentage", () => {
expect(numeralWrapper.formatPercentage(1234.56789)).toEqual("123456.79%"); 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(-987654321)).toEqual("-987.654m");
expect(numeralWrapper.formatReallyBigNumber(-987654321987)).toEqual("-987.654b"); expect(numeralWrapper.formatReallyBigNumber(-987654321987)).toEqual("-987.654b");
expect(numeralWrapper.formatReallyBigNumber(-987654321987654)).toEqual("-987.654t"); expect(numeralWrapper.formatReallyBigNumber(-987654321987654)).toEqual("-987.654t");
expect(numeralWrapper.formatReallyBigNumber(-987654321987654e3)).toEqual("-987.654q"); expect(numeralWrapper.formatReallyBigNumber(-987654321987654000)).toEqual("-987.654q");
expect(numeralWrapper.formatReallyBigNumber(-987654321987654e6)).toEqual("-987.654Q"); expect(numeralWrapper.formatReallyBigNumber(-987654321987654000000)).toEqual("-987.654Q");
expect(numeralWrapper.formatReallyBigNumber(-987654321987654e9)).toEqual("-987.654s"); expect(numeralWrapper.formatReallyBigNumber(-987654321987654000000000)).toEqual("-987.654s");
expect(numeralWrapper.formatReallyBigNumber(-987654321987654e12)).toEqual("-987.654S"); expect(numeralWrapper.formatReallyBigNumber(-987654321987654000000000000)).toEqual("-987.654S");
expect(numeralWrapper.formatReallyBigNumber(-987654321987654e15)).toEqual("-987.654o"); expect(numeralWrapper.formatReallyBigNumber(-987654321987654000000000000000)).toEqual("-987.654o");
expect(numeralWrapper.formatReallyBigNumber(-987654321987654e18)).toEqual("-987.654n"); expect(numeralWrapper.formatReallyBigNumber(-987654321987654000000000000000000)).toEqual("-987.654n");
}); });
test("should format even bigger really big numbers in scientific format", () => { test("should format even bigger really big numbers in scientific format", () => {
expect(numeralWrapper.formatReallyBigNumber(-987654321987654e21)).toEqual("-9.877e+35"); expect(numeralWrapper.formatReallyBigNumber(-987654321987654000000000000000000000)).toEqual("-9.877e+35");
expect(numeralWrapper.formatReallyBigNumber(-987654321987654e22)).toEqual("-9.877e+36"); expect(numeralWrapper.formatReallyBigNumber(-9876543219876540000000000000000000000)).toEqual("-9.877e+36");
expect(numeralWrapper.formatReallyBigNumber(-987654321987654e23)).toEqual("-9.877e+37"); expect(numeralWrapper.formatReallyBigNumber(-98765432198765400000000000000000000000)).toEqual("-9.877e+37");
}); });
test("should format percentage", () => { test("should format percentage", () => {
expect(numeralWrapper.formatPercentage(-1234.56789)).toEqual("-123456.79%"); expect(numeralWrapper.formatPercentage(-1234.56789)).toEqual("-123456.79%");