Merge branch 'dev' of github.com:danielyxie/bitburner into improvement/infiltration-ui

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

@ -1,8 +1,13 @@
// Defined by webpack on startup or compilation
declare let __COMMIT_HASH__: string;
declare const __COMMIT_HASH__: string;
// When using file-loader, we'll get a path to the resource
declare module "*.png" {
const value: string;
export default value;
}
// Achievements communicated back to Electron shell for Steam.
declare interface Document {
achievements: string[];
}

@ -799,5 +799,5 @@ export function calculateAchievements(): void {
// Write all player's achievements to document for Steam/Electron
// This could be replaced by "availableAchievements"
// if we don't want to grant the save game achievements to steam but only currently available
(document as any).achievements = [...Player.achievements.map((a) => a.ID)];
document.achievements = [...Player.achievements.map((a) => a.ID)];
}

@ -109,6 +109,7 @@ export const initSoAAugmentations = (): Augmentation[] => [
rewards, reduced damage taken, etc.
</>
),
isSpecial: true,
factions: [FactionNames.ShadowsOfAnarchy],
}),
new Augmentation({
@ -121,6 +122,7 @@ export const initSoAAugmentations = (): Augmentation[] => [
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({
@ -129,6 +131,7 @@ export const initSoAAugmentations = (): Augmentation[] => [
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({
@ -137,6 +140,7 @@ export const initSoAAugmentations = (): Augmentation[] => [
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({
@ -147,6 +151,7 @@ export const initSoAAugmentations = (): Augmentation[] => [
"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({
@ -155,6 +160,7 @@ export const initSoAAugmentations = (): Augmentation[] => [
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({
@ -163,6 +169,7 @@ export const initSoAAugmentations = (): Augmentation[] => [
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({
@ -176,6 +183,7 @@ export const initSoAAugmentations = (): Augmentation[] => [
position.
</>
),
isSpecial: true,
factions: [FactionNames.ShadowsOfAnarchy],
}),
new Augmentation({
@ -184,6 +192,7 @@ export const initSoAAugmentations = (): Augmentation[] => [
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],
}),
];
@ -242,7 +251,7 @@ export const initGeneralAugmentations = (): Augmentation[] => [
moneyCost: 1.15e8,
repCost: 2.75e4,
info: "The latest version of the 'Augmented Targeting' implant adds the ability to lock-on and track threats.",
prereqs: [AugmentationNames.Targeting2],
prereqs: [AugmentationNames.Targeting2, AugmentationNames.Targeting1],
dexterity_mult: 1.3,
factions: [
FactionNames.TheDarkArmy,
@ -339,7 +348,7 @@ export const initGeneralAugmentations = (): Augmentation[] => [
info:
"The latest version of the 'Combat Rib' augmentation releases advanced anabolic steroids that " +
"improve muscle mass and physical performance while being safe and free of side effects.",
prereqs: [AugmentationNames.CombatRib2],
prereqs: [AugmentationNames.CombatRib2, AugmentationNames.CombatRib1],
strength_mult: 1.18,
defense_mult: 1.18,
factions: [
@ -673,7 +682,7 @@ export const initGeneralAugmentations = (): Augmentation[] => [
"This upgraded firmware allows the Embedded Netburner Module to control information on " +
"a network by re-routing traffic, spoofing IP addresses, and altering the data inside network " +
"packets.",
prereqs: [AugmentationNames.ENMCore],
prereqs: [AugmentationNames.ENMCore, AugmentationNames.ENM],
hacking_speed_mult: 1.05,
hacking_money_mult: 1.3,
hacking_chance_mult: 1.05,
@ -698,7 +707,7 @@ export const initGeneralAugmentations = (): Augmentation[] => [
"The Core V3 library is an implant that upgrades the firmware of the Embedded Netburner Module. " +
"This upgraded firmware allows the Embedded Netburner Module to seamlessly inject code into " +
"any device on a network.",
prereqs: [AugmentationNames.ENMCoreV2],
prereqs: [AugmentationNames.ENMCoreV2, AugmentationNames.ENMCore, AugmentationNames.ENM],
hacking_speed_mult: 1.05,
hacking_money_mult: 1.4,
hacking_chance_mult: 1.1,
@ -826,7 +835,7 @@ export const initGeneralAugmentations = (): Augmentation[] => [
"are a set of specialized microprocessors that are attached to " +
"neurons in the brain. These chips process neural signals to quickly and automatically perform specific computations " +
"so that the brain doesn't have to.",
prereqs: [AugmentationNames.CranialSignalProcessorsG2],
prereqs: [AugmentationNames.CranialSignalProcessorsG2, AugmentationNames.CranialSignalProcessorsG1],
hacking_speed_mult: 1.02,
hacking_money_mult: 1.15,
hacking_mult: 1.09,
@ -841,7 +850,11 @@ export const initGeneralAugmentations = (): Augmentation[] => [
"are a set of specialized microprocessors that are attached to " +
"neurons in the brain. These chips process neural signals to quickly and automatically perform specific computations " +
"so that the brain doesn't have to.",
prereqs: [AugmentationNames.CranialSignalProcessorsG3],
prereqs: [
AugmentationNames.CranialSignalProcessorsG3,
AugmentationNames.CranialSignalProcessorsG2,
AugmentationNames.CranialSignalProcessorsG1,
],
hacking_speed_mult: 1.02,
hacking_money_mult: 1.2,
hacking_grow_mult: 1.25,
@ -856,7 +869,12 @@ export const initGeneralAugmentations = (): Augmentation[] => [
"are a set of specialized microprocessors that are attached to " +
"neurons in the brain. These chips process neural signals to quickly and automatically perform specific computations " +
"so that the brain doesn't have to.",
prereqs: [AugmentationNames.CranialSignalProcessorsG4],
prereqs: [
AugmentationNames.CranialSignalProcessorsG4,
AugmentationNames.CranialSignalProcessorsG3,
AugmentationNames.CranialSignalProcessorsG2,
AugmentationNames.CranialSignalProcessorsG1,
],
hacking_mult: 1.3,
hacking_money_mult: 1.25,
hacking_grow_mult: 1.75,
@ -1254,6 +1272,7 @@ export const initGeneralAugmentations = (): Augmentation[] => [
moneyCost: 0,
info: "It's time to leave the cave.",
stats: null,
isSpecial: true,
factions: [FactionNames.Daedalus],
}),
new Augmentation({
@ -1952,7 +1971,7 @@ export const initChurchOfTheMachineGodAugmentations = (): Augmentation[] => [
"You will become greater than the sum of our parts. As One. Embrace your gift " +
"fully and wholly free of it's accursed toll. Serenity brings tranquility the form " +
"of no longer suffering a stat penalty. ",
prereqs: [AugmentationNames.StaneksGift2],
prereqs: [AugmentationNames.StaneksGift2, AugmentationNames.StaneksGift1],
isSpecial: true,
hacking_chance_mult: 1 / 0.95,
hacking_speed_mult: 1 / 0.95,
@ -2003,6 +2022,7 @@ export function initNeuroFluxGovernor(): Augmentation {
multiplicatively.
</>
),
isSpecial: true,
hacking_chance_mult: 1.01 + donationBonus,
hacking_speed_mult: 1.01 + donationBonus,
hacking_money_mult: 1.01 + donationBonus,

@ -0,0 +1,264 @@
/**
* React component for displaying a single augmentation for purchase through
* the faction UI
*/
import { CheckBox, CheckBoxOutlineBlank, CheckCircle, Info, NewReleases, Report } from "@mui/icons-material";
import { Box, Button, Container, Paper, Tooltip, Typography } from "@mui/material";
import React, { useState } from "react";
import { getNextNeuroFluxLevel } from "../AugmentationHelpers";
import { Faction } from "../../Faction/Faction";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { Settings } from "../../Settings/Settings";
import { numeralWrapper } from "../../ui/numeralFormat";
import { Augmentation } from "../Augmentation";
import { Augmentations } from "../Augmentations";
import { AugmentationNames } from "../data/AugmentationNames";
import { PurchaseAugmentationModal } from "./PurchaseAugmentationModal";
interface IPreReqsProps {
player: IPlayer;
aug: Augmentation;
}
const PreReqs = (props: IPreReqsProps): React.ReactElement => {
const ownedPreReqs = props.aug.prereqs.filter((aug) => props.player.hasAugmentation(aug));
const hasPreReqs = props.aug.prereqs.length > 0 && ownedPreReqs.length === props.aug.prereqs.length;
return (
<Tooltip
title={
<>
<Typography sx={{ color: Settings.theme.money }}>
This Augmentation has the following pre-requisite(s):
</Typography>
{props.aug.prereqs.map((preAug) => (
<Requirement
fulfilled={props.player.hasAugmentation(preAug)}
value={preAug}
color={Settings.theme.money}
key={preAug}
/>
))}
</>
}
>
<Typography
variant="body2"
sx={{
display: "flex",
alignItems: "center",
color: hasPreReqs ? Settings.theme.successlight : Settings.theme.error,
}}
>
{hasPreReqs ? (
<>
<CheckCircle fontSize="small" sx={{ mr: 1 }} />
Pre-requisites Owned
</>
) : (
<>
<Report fontSize="small" sx={{ mr: 1 }} />
Missing {props.aug.prereqs.length - ownedPreReqs.length} pre-requisite(s)
</>
)}
</Typography>
</Tooltip>
);
};
interface IExclusiveProps {
player: IPlayer;
aug: Augmentation;
}
const Exclusive = (props: IExclusiveProps): React.ReactElement => {
return (
<Tooltip
title={
<>
<Typography sx={{ color: Settings.theme.money }}>
This Augmentation can only be acquired from the following source(s):
</Typography>
<ul>
<Typography sx={{ color: Settings.theme.money }}>
<li>
<b>{props.aug.factions[0]}</b> faction
</li>
{props.player.canAccessGang() && !props.aug.isSpecial && (
<li>
Certain <b>gangs</b>
</li>
)}
{props.player.canAccessGrafting() &&
!props.aug.isSpecial &&
props.aug.name !== AugmentationNames.TheRedPill && (
<li>
<b>Grafting</b>
</li>
)}
</Typography>
</ul>
</>
}
>
<NewReleases sx={{ ml: 1, color: Settings.theme.money, transform: "rotate(180deg)" }} />
</Tooltip>
);
};
interface IReqProps {
value: string;
color: string;
fulfilled: boolean;
}
const Requirement = (props: IReqProps): React.ReactElement => {
return (
<Typography sx={{ display: "flex", alignItems: "center", color: props.color }}>
{props.fulfilled ? <CheckBox sx={{ mr: 1 }} /> : <CheckBoxOutlineBlank sx={{ mr: 1 }} />}
{props.value}
</Typography>
);
};
interface IPurchasableAugsProps {
augNames: string[];
ownedAugNames: string[];
player: IPlayer;
canPurchase: (player: IPlayer, aug: Augmentation) => boolean;
purchaseAugmentation: (player: IPlayer, aug: Augmentation, showModal: (open: boolean) => void) => void;
rep?: number;
sleeveAugs?: boolean;
faction?: Faction;
}
export const PurchasableAugmentations = (props: IPurchasableAugsProps): React.ReactElement => {
return (
<Container
maxWidth="lg"
disableGutters
sx={{ mx: 0, display: "grid", gridTemplateColumns: "repeat(1, 1fr)", gap: 1 }}
>
{props.augNames.map((augName: string) => (
<PurchasableAugmentation key={augName} parent={props} augName={augName} owned={false} />
))}
{props.ownedAugNames.map((augName: string) => (
<PurchasableAugmentation key={augName} parent={props} augName={augName} owned={true} />
))}
</Container>
);
};
interface IPurchasableAugProps {
parent: IPurchasableAugsProps;
augName: string;
owned: boolean;
}
export function PurchasableAugmentation(props: IPurchasableAugProps): React.ReactElement {
const [open, setOpen] = useState(false);
const aug = Augmentations[props.augName];
const cost = props.parent.sleeveAugs ? aug.startingCost : aug.baseCost;
const info = typeof aug.info === "string" ? <span>{aug.info}</span> : aug.info;
const description = (
<>
{info}
<br />
<br />
{aug.stats}
</>
);
return (
<Paper
sx={{
p: 1,
display: "grid",
gridTemplateColumns: "minmax(0, 4fr) 1fr",
gap: 1,
opacity: props.owned ? 0.75 : 1,
}}
>
<>
<Box sx={{ display: "flex", alignItems: "center" }}>
<Button
onClick={() =>
props.parent.purchaseAugmentation(props.parent.player, aug, (open): void => {
setOpen(open);
})
}
disabled={!props.parent.canPurchase(props.parent.player, aug) || props.owned}
sx={{ width: "48px", height: "48px", float: "left", clear: "none", mr: 1 }}
>
{props.owned ? "Owned" : "Buy"}
</Button>
<Box sx={{ maxWidth: props.owned ? "100%" : "85%" }}>
<Box sx={{ display: "flex", alignItems: "center" }}>
<Tooltip
title={
<>
<Typography variant="h5">
{props.augName}
{props.augName === AugmentationNames.NeuroFluxGovernor && ` - Level ${getNextNeuroFluxLevel()}`}
</Typography>
<Typography>{description}</Typography>
</>
}
>
<Info sx={{ mr: 1 }} color="info" />
</Tooltip>
<Typography
variant="h6"
sx={{
textOverflow: "ellipsis",
whiteSpace: "nowrap",
overflow: "hidden",
}}
>
{aug.name}
{aug.name === AugmentationNames.NeuroFluxGovernor && ` - Level ${getNextNeuroFluxLevel()}`}
</Typography>
{aug.factions.length === 1 && !props.parent.sleeveAugs && (
<Exclusive player={props.parent.player} aug={aug} />
)}
</Box>
{aug.prereqs.length > 0 && !props.parent.sleeveAugs && <PreReqs player={props.parent.player} aug={aug} />}
</Box>
</Box>
{props.owned || (
<Box sx={{ display: "grid", alignItems: "center", justifyItems: "left" }}>
<Requirement
fulfilled={aug.baseCost === 0 || props.parent.player.money > cost}
value={numeralWrapper.formatMoney(cost)}
color={Settings.theme.money}
/>
{props.parent.rep !== undefined && (
<Requirement
fulfilled={props.parent.rep >= aug.baseRepRequirement}
value={`${numeralWrapper.formatReputation(aug.baseRepRequirement)} rep`}
color={Settings.theme.rep}
/>
)}
</Box>
)}
{Settings.SuppressBuyAugmentationConfirmation || (
<PurchaseAugmentationModal
open={open}
onClose={() => setOpen(false)}
faction={props.parent.faction}
aug={aug}
/>
)}
</>
</Paper>
);
}

@ -1,9 +1,9 @@
import React from "react";
import { Augmentation } from "../../Augmentation/Augmentation";
import { Faction } from "../Faction";
import { purchaseAugmentation } from "../FactionHelpers";
import { isRepeatableAug } from "../../Augmentation/AugmentationHelpers";
import { Augmentation } from "../Augmentation";
import { Faction } from "../../Faction/Faction";
import { purchaseAugmentation } from "../../Faction/FactionHelpers";
import { isRepeatableAug } from "../AugmentationHelpers";
import { Money } from "../../ui/React/Money";
import { Modal } from "../../ui/React/Modal";
import { use } from "../../ui/Context";
@ -13,21 +13,23 @@ import Button from "@mui/material/Button";
interface IProps {
open: boolean;
onClose: () => void;
faction: Faction;
aug: Augmentation;
rerender: () => void;
faction?: Faction;
aug?: Augmentation;
}
export function PurchaseAugmentationModal(props: IProps): React.ReactElement {
if (typeof props.aug === "undefined" || typeof props.faction === "undefined") {
return <></>;
}
const player = use.Player();
function buy(): void {
if (!isRepeatableAug(props.aug) && player.hasAugmentation(props.aug)) {
if (!isRepeatableAug(props.aug as Augmentation) && player.hasAugmentation(props.aug as Augmentation)) {
return;
}
purchaseAugmentation(props.aug, props.faction);
props.rerender();
purchaseAugmentation(props.aug as Augmentation, props.faction as Faction);
props.onClose();
}

@ -163,7 +163,9 @@ export function BitverseRoot(props: IProps): React.ReactElement {
return lvl;
}
const max = n === 12 ? Infinity : 3;
return Math.min(max, lvl + 1);
// If accessed via flume, display the current BN level, else the next
return Math.min(max, lvl + Number(!props.flume));
};
if (Settings.DisableASCIIArt) {

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

@ -120,7 +120,7 @@ export const CONSTANTS: {
LatestUpdate: string;
} = {
VersionString: "1.6.4",
VersionNumber: 15,
VersionNumber: 16,
// Speed (in ms) at which the main loop is updated
_idleSpeed: 200,
@ -293,7 +293,7 @@ export const CONSTANTS: {
// BitNode/Source-File related stuff
TotalNumBitNodes: 24,
Donations: 4,
Donations: 6,
LatestUpdate: `
v1.6.3 - 2022-04-01 Few stanek fixes

@ -11,11 +11,41 @@ import { exportScripts } from "./Terminal/commands/download";
import { CONSTANTS } from "./Constants";
import { hash } from "./hash/hash";
interface IReturnWebStatus extends IReturnStatus {
data?: Record<string, unknown>;
}
declare global {
interface Window {
appNotifier: {
terminal: (message: string, type?: string) => void;
toast: (message: string, type: ToastVariant, duration?: number) => void;
};
appSaveFns: {
triggerSave: () => Promise<void>;
triggerGameExport: () => void;
triggerScriptsExport: () => void;
getSaveData: () => { save: string; fileName: string };
getSaveInfo: (base64save: string) => Promise<ImportPlayerData | undefined>;
pushSaveData: (base64save: string, automatic?: boolean) => void;
};
electronBridge: {
send: (channel: string, data?: unknown) => void;
receive: (channel: string, func: (...args: any[]) => void) => void;
};
}
interface Document {
getFiles: () => IReturnWebStatus;
deleteFile: (filename: string) => IReturnWebStatus;
saveFile: (filename: string, code: string) => IReturnWebStatus;
}
}
export function initElectron(): void {
const userAgent = navigator.userAgent.toLowerCase();
if (userAgent.indexOf(" electron/") > -1) {
// Electron-specific code
(document as any).achievements = [];
document.achievements = [];
initWebserver();
initAppNotifier();
initSaveFunctions();
@ -24,11 +54,6 @@ export function initElectron(): void {
}
function initWebserver(): void {
interface IReturnWebStatus extends IReturnStatus {
data?: {
[propName: string]: any;
};
}
function normalizeFileName(filename: string): string {
filename = filename.replace(/\/\/+/g, "/");
filename = removeLeadingSlash(filename);
@ -38,7 +63,7 @@ function initWebserver(): void {
return filename;
}
(document as any).getFiles = function (): IReturnWebStatus {
document.getFiles = function (): IReturnWebStatus {
const home = GetServer("home");
if (home === null) {
return {
@ -58,7 +83,7 @@ function initWebserver(): void {
};
};
(document as any).deleteFile = function (filename: string): IReturnWebStatus {
document.deleteFile = function (filename: string): IReturnWebStatus {
filename = normalizeFileName(filename);
const home = GetServer("home");
if (home === null) {
@ -70,7 +95,7 @@ function initWebserver(): void {
return home.removeFile(filename);
};
(document as any).saveFile = function (filename: string, code: string): IReturnWebStatus {
document.saveFile = function (filename: string, code: string): IReturnWebStatus {
filename = normalizeFileName(filename);
code = Buffer.from(code, "base64").toString();
@ -115,7 +140,7 @@ function initAppNotifier(): void {
};
// Will be consumud by the electron wrapper.
(window as any).appNotifier = funcs;
window.appNotifier = funcs;
}
function initSaveFunctions(): void {
@ -149,38 +174,38 @@ function initSaveFunctions(): void {
};
// Will be consumud by the electron wrapper.
(window as any).appSaveFns = funcs;
window.appSaveFns = funcs;
}
function initElectronBridge(): void {
const bridge = (window as any).electronBridge as any;
const bridge = window.electronBridge;
if (!bridge) return;
bridge.receive("get-save-data-request", () => {
const data = (window as any).appSaveFns.getSaveData();
const data = window.appSaveFns.getSaveData();
bridge.send("get-save-data-response", data);
});
bridge.receive("get-save-info-request", async (save: string) => {
const data = await (window as any).appSaveFns.getSaveInfo(save);
const data = await window.appSaveFns.getSaveInfo(save);
bridge.send("get-save-info-response", data);
});
bridge.receive("push-save-request", ({ save, automatic = false }: { save: string; automatic: boolean }) => {
(window as any).appSaveFns.pushSaveData(save, automatic);
window.appSaveFns.pushSaveData(save, automatic);
});
bridge.receive("trigger-save", () => {
return (window as any).appSaveFns
return window.appSaveFns
.triggerSave()
.then(() => {
bridge.send("save-completed");
})
.catch((error: any) => {
.catch((error: unknown) => {
console.log(error);
SnackbarEvents.emit("Could not save game.", ToastVariant.ERROR, 2000);
});
});
bridge.receive("trigger-game-export", () => {
try {
(window as any).appSaveFns.triggerGameExport();
window.appSaveFns.triggerGameExport();
} catch (error) {
console.log(error);
SnackbarEvents.emit("Could not export game.", ToastVariant.ERROR, 2000);
@ -188,7 +213,7 @@ function initElectronBridge(): void {
});
bridge.receive("trigger-scripts-export", () => {
try {
(window as any).appSaveFns.triggerScriptsExport();
window.appSaveFns.triggerScriptsExport();
} catch (error) {
console.log(error);
SnackbarEvents.emit("Could not export scripts.", ToastVariant.ERROR, 2000);
@ -197,14 +222,14 @@ function initElectronBridge(): void {
}
export function pushGameSaved(data: SaveData): void {
const bridge = (window as any).electronBridge as any;
const bridge = window.electronBridge;
if (!bridge) return;
bridge.send("push-game-saved", data);
}
export function pushGameReady(): void {
const bridge = (window as any).electronBridge as any;
const bridge = window.electronBridge;
if (!bridge) return;
// Send basic information to the electron wrapper
@ -222,7 +247,7 @@ export function pushGameReady(): void {
}
export function pushImportResult(wasImported: boolean): void {
const bridge = (window as any).electronBridge as any;
const bridge = window.electronBridge;
if (!bridge) return;
bridge.send("push-import-result", { wasImported });
@ -230,7 +255,7 @@ export function pushImportResult(wasImported: boolean): void {
}
export function pushDisableRestore(): void {
const bridge = (window as any).electronBridge as any;
const bridge = window.electronBridge;
if (!bridge) return;
bridge.send("push-disable-restore", { duration: 1000 * 60 });

@ -54,36 +54,15 @@ export function joinFaction(faction: Faction): void {
//Returns a boolean indicating whether the player has the prerequisites for the
//specified Augmentation
export function hasAugmentationPrereqs(aug: Augmentation): boolean {
let hasPrereqs = true;
if (aug.prereqs && aug.prereqs.length > 0) {
for (let i = 0; i < aug.prereqs.length; ++i) {
const prereqAug = Augmentations[aug.prereqs[i]];
if (prereqAug == null) {
console.error(`Invalid prereq Augmentation ${aug.prereqs[i]}`);
continue;
}
if (Player.hasAugmentation(prereqAug, true) === false) {
hasPrereqs = false;
// Check if the aug is purchased
for (let j = 0; j < Player.queuedAugmentations.length; ++j) {
if (Player.queuedAugmentations[j].name === prereqAug.name) {
hasPrereqs = true;
break;
}
}
}
}
}
return hasPrereqs;
return aug.prereqs.every((aug) => Player.hasAugmentation(aug));
}
export function purchaseAugmentation(aug: Augmentation, fac: Faction, sing = false): string {
const hasPrereqs = hasAugmentationPrereqs(aug);
if (!hasPrereqs) {
const txt = `You must first purchase or install ${aug.prereqs.join(",")} before you can purchase this one.`;
const txt = `You must first purchase or install ${aug.prereqs
.filter((req) => !Player.hasAugmentation(req))
.join(",")} before you can purchase this one.`;
if (sing) {
return txt;
} else {
@ -165,13 +144,11 @@ export const getFactionAugmentationsFiltered = (player: IPlayer, faction: Factio
let augs = Object.values(Augmentations);
// 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
blacklist.push(AugmentationNames.TheRedPill);
augs.push(Augmentations[AugmentationNames.TheRedPill]);
}
const rng = SFC32RNG(`BN${player.bitNodeN}.${player.sourceFileLvl(player.bitNodeN)}`);
@ -190,9 +167,6 @@ export const getFactionAugmentationsFiltered = (player: IPlayer, faction: Factio
};
augs = augs.filter(uniqueFilter);
// Remove blacklisted augs
augs = augs.filter((a) => !blacklist.includes(a.name));
return augs.map((a) => a.name);
}

@ -1,30 +1,21 @@
/**
* Root React Component for displaying a faction's "Purchase Augmentations" page
*/
import { Box, Button, Tooltip, Typography, Paper, Container } from "@mui/material";
import React, { useState } from "react";
import { PurchaseableAugmentation } from "./PurchaseableAugmentation";
import { getGenericAugmentationPriceMultiplier } from "../../Augmentation/AugmentationHelpers";
import { Augmentations } from "../../Augmentation/Augmentations";
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
import { Faction } from "../Faction";
import { PurchasableAugmentations } from "../../Augmentation/ui/PurchasableAugmentations";
import { PurchaseAugmentationsOrderSetting } from "../../Settings/SettingEnums";
import { Settings } from "../../Settings/Settings";
import { hasAugmentationPrereqs, getFactionAugmentationsFiltered } from "../FactionHelpers";
import { use } from "../../ui/Context";
import { Reputation } from "../../ui/React/Reputation";
import { Favor } from "../../ui/React/Favor";
import { numeralWrapper } from "../../ui/numeralFormat";
import Box from "@mui/material/Box";
import Button from "@mui/material/Button";
import Typography from "@mui/material/Typography";
import Tooltip from "@mui/material/Tooltip";
import TableBody from "@mui/material/TableBody";
import Table from "@mui/material/Table";
import { getGenericAugmentationPriceMultiplier } from "../../Augmentation/AugmentationHelpers";
import { Favor } from "../../ui/React/Favor";
import { Reputation } from "../../ui/React/Reputation";
import { FactionNames } from "../data/FactionNames";
import { Faction } from "../Faction";
import { getFactionAugmentationsFiltered, hasAugmentationPrereqs, purchaseAugmentation } from "../FactionHelpers";
type IProps = {
faction: Faction;
@ -137,38 +128,12 @@ export function AugmentationsPage(props: IProps): React.ReactElement {
aug === AugmentationNames.NeuroFluxGovernor ||
(!player.augmentations.some((a) => a.name === aug) && !player.queuedAugmentations.some((a) => a.name === aug)),
);
const purchaseableAugmentation = (aug: string, owned = false): React.ReactNode => {
return (
<PurchaseableAugmentation
augName={aug}
faction={props.faction}
key={aug}
p={player}
rerender={rerender}
owned={owned}
/>
);
};
const augListElems = purchasable.map((aug) => purchaseableAugmentation(aug));
let ownedElem = <></>;
const owned = augs.filter((aug: string) => !purchasable.includes(aug));
if (owned.length !== 0) {
ownedElem = (
<>
<br />
<Typography variant="h4">Purchased Augmentations</Typography>
<Typography>This faction also offers these augmentations but you already own them.</Typography>
{owned.map((aug) => purchaseableAugmentation(aug, true))}
</>
);
}
const multiplierComponent =
props.faction.name !== FactionNames.ShadowsOfAnarchy ? (
<Typography>
Price multiplier: x {numeralWrapper.formatMultiplier(getGenericAugmentationPriceMultiplier())}
<b>Price multiplier:</b> x {numeralWrapper.formatReallyBigNumber(getGenericAugmentationPriceMultiplier())}
</Typography>
) : (
<></>
@ -176,42 +141,77 @@ export function AugmentationsPage(props: IProps): React.ReactElement {
return (
<>
<Container disableGutters maxWidth="lg" sx={{ mx: 0 }}>
<Button onClick={props.routeToMainPage}>Back</Button>
<Typography variant="h4">Faction Augmentations</Typography>
<Paper sx={{ p: 1, mb: 1 }}>
<Typography>
These are all of the Augmentations that are available to purchase from {props.faction.name}. Augmentations are
powerful upgrades that will enhance your abilities.
These are all of the Augmentations that are available to purchase from <b>{props.faction.name}</b>.
Augmentations are powerful upgrades that will enhance your abilities.
<br />
Reputation: <Reputation reputation={props.faction.playerReputation} /> Favor:{" "}
<Favor favor={Math.floor(props.faction.favor)} />
</Typography>
<Box display="flex">
<Box
sx={{
display: "grid",
gridTemplateColumns: `repeat(${props.faction.name === FactionNames.ShadowsOfAnarchy ? "2" : "3"}, 1fr)`,
justifyItems: "center",
my: 1,
}}
>
<Tooltip
title={
<Typography>
The price of every Augmentation increases for every queued Augmentation and it is reset when you install
them.
The price of every Augmentation increases for every queued Augmentation and it is reset when you
install them.
</Typography>
}
>
{multiplierComponent}
</Tooltip>
<Typography>
<b>Reputation:</b> <Reputation reputation={props.faction.playerReputation} />
</Typography>
<Typography>
<b>Favor:</b> <Favor favor={Math.floor(props.faction.favor)} />
</Typography>
</Box>
<Box sx={{ display: "grid", gridTemplateColumns: "repeat(4, 1fr)" }}>
<Button onClick={() => switchSortOrder(PurchaseAugmentationsOrderSetting.Cost)}>Sort by Cost</Button>
<Button onClick={() => switchSortOrder(PurchaseAugmentationsOrderSetting.Reputation)}>Sort by Reputation</Button>
<Button onClick={() => switchSortOrder(PurchaseAugmentationsOrderSetting.Default)}>Sort by Default Order</Button>
<Button onClick={() => switchSortOrder(PurchaseAugmentationsOrderSetting.Reputation)}>
Sort by Reputation
</Button>
<Button onClick={() => switchSortOrder(PurchaseAugmentationsOrderSetting.Default)}>
Sort by Default Order
</Button>
<Button onClick={() => switchSortOrder(PurchaseAugmentationsOrderSetting.Purchasable)}>
Sort by Purchasable
</Button>
<br />
</Box>
</Paper>
</Container>
<Table size="small" padding="none">
<TableBody>{augListElems}</TableBody>
</Table>
<Table size="small" padding="none">
<TableBody>{ownedElem}</TableBody>
</Table>
<PurchasableAugmentations
augNames={purchasable}
ownedAugNames={owned}
player={player}
canPurchase={(player, aug) => {
return (
hasAugmentationPrereqs(aug) &&
props.faction.playerReputation >= aug.baseRepRequirement &&
(aug.baseCost === 0 || player.money > aug.baseCost)
);
}}
purchaseAugmentation={(player, aug, showModal) => {
if (!Settings.SuppressBuyAugmentationConfirmation) {
showModal(true);
} else {
purchaseAugmentation(aug, props.faction);
rerender();
}
}}
rep={props.faction.playerReputation}
faction={props.faction}
/>
</>
);
}

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

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

@ -1,170 +0,0 @@
/**
* React component for displaying a single augmentation for purchase through
* the faction UI
*/
import React, { useState } from "react";
import { hasAugmentationPrereqs, purchaseAugmentation } from "../FactionHelpers";
import { PurchaseAugmentationModal } from "./PurchaseAugmentationModal";
import { Augmentations } from "../../Augmentation/Augmentations";
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
import { Faction } from "../Faction";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { Settings } from "../../Settings/Settings";
import { Money } from "../../ui/React/Money";
import { Reputation } from "../../ui/React/Reputation";
import { Augmentation as AugFormat } from "../../ui/React/Augmentation";
import Button from "@mui/material/Button";
import Typography from "@mui/material/Typography";
import Tooltip from "@mui/material/Tooltip";
import Box from "@mui/material/Box";
import { TableCell } from "../../ui/React/Table";
import TableRow from "@mui/material/TableRow";
import { getNextNeuroFluxLevel } from "../../Augmentation/AugmentationHelpers";
interface IReqProps {
augName: string;
p: IPlayer;
hasReq: boolean;
rep: number;
hasRep: boolean;
cost: number;
hasCost: boolean;
}
function Requirements(props: IReqProps): React.ReactElement {
const aug = Augmentations[props.augName];
if (!props.hasReq) {
return (
<TableCell key={1} colSpan={2}>
<Typography color="error">
Requires{" "}
{aug.prereqs.map((aug, i) => (
<AugFormat key={i} name={aug} />
))}
</Typography>
</TableCell>
);
}
return (
<React.Fragment key="f">
<TableCell key={1}>
<Typography>
<Money money={props.cost} player={props.p} />
</Typography>
</TableCell>
<TableCell key={2}>
<Typography color={props.hasRep ? "primary" : "error"}>
Requires <Reputation reputation={props.rep} /> faction reputation
</Typography>
</TableCell>
</React.Fragment>
);
}
interface IProps {
augName: string;
faction: Faction;
p: IPlayer;
rerender: () => void;
owned?: boolean;
}
export function PurchaseableAugmentation(props: IProps): React.ReactElement {
const [open, setOpen] = useState(false);
const aug = Augmentations[props.augName];
if (aug == null) throw new Error(`aug ${props.augName} does not exists`);
if (aug == null) {
console.error(
`Invalid Augmentation when trying to create PurchaseableAugmentation display element: ${props.augName}`,
);
return <></>;
}
const moneyCost = aug.baseCost;
const repCost = aug.baseRepRequirement;
const hasReq = hasAugmentationPrereqs(aug);
const hasRep = props.faction.playerReputation >= repCost;
const hasCost = aug.baseCost === 0 || props.p.money > aug.baseCost;
// Determine UI properties
const color: "error" | "primary" = !hasReq || !hasRep || !hasCost ? "error" : "primary";
// Determine button txt
let btnTxt = aug.name;
if (aug.name === AugmentationNames.NeuroFluxGovernor) {
btnTxt += ` - Level ${getNextNeuroFluxLevel()}`;
}
let tooltip = <></>;
if (typeof aug.info === "string") {
tooltip = (
<>
<span>{aug.info}</span>
<br />
<br />
{aug.stats}
</>
);
} else
tooltip = (
<>
{aug.info}
<br />
<br />
{aug.stats}
</>
);
function handleClick(): void {
if (color === "error") return;
if (!Settings.SuppressBuyAugmentationConfirmation) {
setOpen(true);
} else {
purchaseAugmentation(aug, props.faction);
props.rerender();
}
}
return (
<TableRow>
{!props.owned && (
<TableCell key={0}>
<Button onClick={handleClick} color={color}>
Buy
</Button>
<PurchaseAugmentationModal
open={open}
onClose={() => setOpen(false)}
aug={aug}
faction={props.faction}
rerender={props.rerender}
/>
</TableCell>
)}
<TableCell key={1}>
<Box display="flex">
<Tooltip title={<Typography>{tooltip}</Typography>} placement="top">
<Typography>{btnTxt}</Typography>
</Tooltip>
</Box>
</TableCell>
{!props.owned && (
<Requirements
key={2}
augName={props.augName}
p={props.p}
cost={moneyCost}
rep={repCost}
hasReq={hasReq}
hasRep={hasRep}
hasCost={hasCost}
/>
)}
</TableRow>
);
}

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

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

@ -17,7 +17,7 @@ export function calculateSellInformationCashReward(
Math.pow(difficulty, 3) *
3e3 *
levelBonus *
(player.hasAugmentation(AugmentationNames.WKSharmonizer) ? 1.5 : 1) *
(player.hasAugmentation(AugmentationNames.WKSharmonizer, true) ? 1.5 : 1) *
BitNodeMultipliers.InfiltrationMoney
);
}
@ -31,11 +31,11 @@ export function calculateTradeInformationRepReward(
const levelBonus = maxLevel * Math.pow(1.01, maxLevel);
return (
Math.pow(reward + 1, 2) *
Math.pow(difficulty, 3) *
3e3 *
Math.pow(reward + 1, 1.1) *
Math.pow(difficulty, 1.2) *
30 *
levelBonus *
(player.hasAugmentation(AugmentationNames.WKSharmonizer) ? 1.5 : 1) *
(player.hasAugmentation(AugmentationNames.WKSharmonizer, true) ? 1.5 : 1) *
BitNodeMultipliers.InfiltrationMoney
);
}
@ -47,5 +47,7 @@ export function calculateInfiltratorsRepReward(player: IPlayer, faction: Faction
}, 0);
const baseRepGain = (difficulty / maxStartingSecurityLevel) * 5000;
return baseRepGain * (player.hasAugmentation(AugmentationNames.WKSharmonizer) ? 2 : 1) * (1 + faction.favor / 100);
return (
baseRepGain * (player.hasAugmentation(AugmentationNames.WKSharmonizer, true) ? 2 : 1) * (1 + faction.favor / 100)
);
}

@ -92,7 +92,7 @@ export function Game(props: IProps): React.ReactElement {
// it's clear they're not meant to
const damage = options?.automated
? player.hp
: props.StartingDifficulty * 3 * (player.hasAugmentation(AugmentationNames.WKSharmonizer) ? 0.5 : 1);
: props.StartingDifficulty * 3 * (player.hasAugmentation(AugmentationNames.WKSharmonizer, true) ? 0.5 : 1);
if (player.takeDamage(damage)) {
router.toCity();
return;

@ -24,7 +24,7 @@ interface IProps {
export function GameTimer(props: IProps): React.ReactElement {
const player = use.Player();
const [v, setV] = useState(100);
const totalMillis = (player.hasAugmentation(AugmentationNames.WKSharmonizer) ? 1.3 : 1) * props.millis;
const totalMillis = (player.hasAugmentation(AugmentationNames.WKSharmonizer, true) ? 1.3 : 1) * props.millis;
const tick = 200;
useEffect(() => {

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

@ -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
// 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));
// Push the blob URL onto the top of the stack.

@ -1,15 +1,23 @@
import { Augmentations } from "../../Augmentation/Augmentations";
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
import { GraftableAugmentation } from "./GraftableAugmentation";
import { IPlayer } from "../IPlayer";
export const getGraftingAvailableAugs = (player: IPlayer): string[] => {
const augs: string[] = [];
for (const [augName, aug] of Object.entries(Augmentations)) {
if (augName === AugmentationNames.NeuroFluxGovernor || augName === AugmentationNames.TheRedPill || aug.isSpecial)
continue;
if (aug.isSpecial) continue;
augs.push(augName);
}
return augs.filter((augmentation: string) => !player.hasAugmentation(augmentation));
};
export const graftingIntBonus = (player: IPlayer): number => {
return 1 + (player.getIntelligenceBonus(3) - 1) / 3;
};
export const calculateGraftingTimeWithBonus = (player: IPlayer, aug: GraftableAugmentation): number => {
const baseTime = aug.time;
return baseTime / graftingIntBonus(player);
};

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

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

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

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

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

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

@ -5,7 +5,6 @@ import { IPlayer } from "../IPlayer";
import { Augmentation } from "../../Augmentation/Augmentation";
import { Augmentations } from "../../Augmentation/Augmentations";
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
import { Faction } from "../../Faction/Faction";
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
// and augs that aren't allowed for sleeves
function isAvailableForSleeve(aug: Augmentation): boolean {
if (aug.name === AugmentationNames.NeuroFluxGovernor) {
return false;
}
if (ownedAugNames.includes(aug.name)) {
return false;
}

@ -1,20 +1,10 @@
import React, { useState, useEffect } from "react";
import { Container, Typography, Paper } from "@mui/material";
import React, { useEffect, useState } from "react";
import { PurchasableAugmentations } from "../../../Augmentation/ui/PurchasableAugmentations";
import { use } from "../../../ui/Context";
import { Modal } from "../../../ui/React/Modal";
import { Sleeve } from "../Sleeve";
import { findSleevePurchasableAugs } from "../SleeveHelpers";
import { Augmentations } from "../../../Augmentation/Augmentations";
import { Augmentation } from "../../../Augmentation/Augmentation";
import { Money } from "../../../ui/React/Money";
import { Modal } from "../../../ui/React/Modal";
import { use } from "../../../ui/Context";
import Typography from "@mui/material/Typography";
import Tooltip from "@mui/material/Tooltip";
import Paper from "@mui/material/Paper";
import Box from "@mui/material/Box";
import Button from "@mui/material/Button";
import TableBody from "@mui/material/TableBody";
import Table from "@mui/material/Table";
import { TableCell } from "../../../ui/React/Table";
import TableRow from "@mui/material/TableRow";
interface IProps {
open: boolean;
@ -42,15 +32,9 @@ export function SleeveAugmentationsModal(props: IProps): React.ReactElement {
// and you must also have enough rep in that faction in order to purchase it.
const availableAugs = findSleevePurchasableAugs(props.sleeve, player);
function purchaseAugmentation(aug: Augmentation): void {
props.sleeve.tryBuyAugmentation(player, aug);
rerender();
}
return (
<Modal open={props.open} onClose={props.onClose}>
<>
<Box sx={{ mx: 1 }}>
<Container component={Paper} disableGutters maxWidth="lg" sx={{ mx: 0, mb: 1, p: 1 }}>
<Typography>
You can purchase Augmentations for your Duplicate Sleeves. These Augmentations have the same effect as they
would for you. You can only purchase Augmentations that you have unlocked through Factions.
@ -58,64 +42,24 @@ export function SleeveAugmentationsModal(props: IProps): React.ReactElement {
<br />
When purchasing an Augmentation for a Duplicate Sleeve, they are immediately installed. This means that the
Duplicate Sleeve will immediately lose all of its stat experience.
<br />
<br />
Augmentations will appear below as they become available.
</Typography>
<Box component={Paper} sx={{ my: 1, p: 1 }}>
<Table size="small" padding="none">
<TableBody>
{availableAugs.map((aug) => {
return (
<TableRow key={aug.name}>
<TableCell>
<Button onClick={() => purchaseAugmentation(aug)} disabled={player.money < aug.startingCost}>
Buy
</Button>
</TableCell>
<TableCell>
<Box display="flex">
<Tooltip title={aug.stats || ""}>
<Typography>{aug.name}</Typography>
</Tooltip>
</Box>
</TableCell>
<TableCell>
<Money money={aug.startingCost} player={player} />
</TableCell>
</TableRow>
);
})}
</TableBody>
</Table>
</Box>
</Box>
{ownedAugNames.length > 0 && (
<>
<Typography sx={{ mx: 1 }}>Owned Augmentations:</Typography>
<Box display="grid" sx={{ gridTemplateColumns: "repeat(5, 1fr)", m: 1 }}>
{ownedAugNames.map((augName) => {
const aug = Augmentations[augName];
const info = typeof aug.info === "string" ? <span>{aug.info}</span> : aug.info;
const tooltip = (
<>
{info}
<br />
<br />
{aug.stats}
</>
);
return (
<Tooltip key={augName} title={<Typography>{tooltip}</Typography>}>
<Paper sx={{ p: 1 }}>
<Typography>{augName}</Typography>
</Paper>
</Tooltip>
);
})}
</Box>
</>
)}
</>
</Container>
<PurchasableAugmentations
augNames={availableAugs.map((aug) => aug.name)}
ownedAugNames={ownedAugNames}
player={player}
canPurchase={(player, aug) => {
return player.money > aug.startingCost;
}}
purchaseAugmentation={(player, aug, _showModal) => {
props.sleeve.tryBuyAugmentation(player, aug);
rerender();
}}
sleeveAugs
/>
</Modal>
);
}

@ -110,6 +110,8 @@ const tasks: {
first: factions,
second: (s1: string) => {
const faction = Factions[s1];
if (!faction) return ["------"];
const facInfo = faction.getInfo();
const options: string[] = [];
if (facInfo.offerHackingWork) {
@ -260,7 +262,7 @@ export function TaskSelector(props: IProps): React.ReactElement {
const detailsF = tasks[n];
if (detailsF === undefined) throw new Error(`No function for task '${s0}'`);
const details = detailsF(props.player, props.sleeve);
const details2 = details.second(details.first[0]);
const details2 = details.second(details.first[0]) ?? ["------"];
setS2(details2[0]);
setS1(details.first[0]);
setS0(n);

@ -400,8 +400,19 @@ function evaluateVersionCompatibility(ver: string | number): void {
if (ver < 15) {
(Settings as any).EditorTheme = { ...defaultMonacoTheme };
}
//Fix contract names
if (ver < 16) {
Factions[FactionNames.ShadowsOfAnarchy] = new Faction(FactionNames.ShadowsOfAnarchy);
//Iterate over all contracts on all servers
for (const server of GetAllServers()) {
for (const contract of server.contracts) {
//Rename old "HammingCodes: Integer to encoded Binary" contracts
//to "HammingCodes: Integer to Encoded Binary"
if (contract.type == "HammingCodes: Integer to encoded Binary") {
contract.type = "HammingCodes: Integer to Encoded Binary";
}
}
}
}
}
}

@ -2198,7 +2198,7 @@ export interface Singularity {
* RAM cost: 5 GB * 16/4/1
*
*
* This function will automatically install your Augmentations, resetting the game as usual.
* This function will automatically install your Augmentations, resetting the game as usual. If you do not own uninstalled Augmentations then the game will not reset.
*
* @param cbScript - This is a script that will automatically be run after Augmentations are installed (after the reset). This script will be run with no arguments and 1 thread. It must be located on your home computer.
*/

@ -1,6 +1,7 @@
import { getRandomInt } from "../utils/helpers/getRandomInt";
import { MinHeap } from "../utils/Heap";
import { comprGenChar, comprLZGenerate, comprLZEncode, comprLZDecode } from "../utils/CompressionContracts";
import { HammingEncode, HammingDecode } from "../utils/HammingCodeTools";
/* tslint:disable:completed-docs no-magic-numbers arrow-return-shorthand */
@ -1249,7 +1250,7 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [
},
},
{
name: "HammingCodes: Integer to encoded Binary",
name: "HammingCodes: Integer to Encoded Binary",
numTries: 10,
difficulty: 5,
desc: (n: number): string => {
@ -1456,4 +1457,155 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [
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 { LoadingScreen } from "./ui/LoadingScreen";
import { initElectron } from "./Electron";
import { AlertEvents } from "./ui/React/AlertManager";
initElectron();
globalThis["React"] = React;
globalThis["ReactDOM"] = ReactDOM;
@ -34,3 +35,9 @@ function rerender(): void {
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",
flexDirection: "column-reverse",
},
titleButton: {
padding: "1px 6px",
},
success: {
color: theme.colors.success,
},
@ -260,35 +263,33 @@ function LogWindow(props: IProps): React.ReactElement {
}}
>
<Box className="drag" display="flex" alignItems="center" ref={draggableRef}>
<Typography color="primary" variant="h6" title={title(true)}>
<Typography color="primary" variant="h6" sx={{ marginRight: "auto" }} title={title(true)}>
{title()}
</Typography>
<Box position="absolute" right={0}>
{!workerScripts.has(script.pid) && (
<Button onClick={run} onTouchEnd={run}>
{!workerScripts.has(script.pid) ? (
<Button className={classes.titleButton} onClick={run} onTouchEnd={run}>
Run
</Button>
)}
{workerScripts.has(script.pid) && (
<Button onClick={kill} onTouchEnd={kill}>
) : (
<Button className={classes.titleButton} onClick={kill} onTouchEnd={kill}>
Kill
</Button>
)}
<Button onClick={minimize} onTouchEnd={minimize}>
<Button className={classes.titleButton} onClick={minimize} onTouchEnd={minimize}>
{minimized ? "\u{1F5D6}" : "\u{1F5D5}"}
</Button>
<Button onClick={props.onClose} onTouchEnd={props.onClose}>
<Button className={classes.titleButton} onClick={props.onClose} onTouchEnd={props.onClose}>
Close
</Button>
</Box>
</Box>
</Paper>
<Paper sx={{ overflow: "scroll", overflowWrap: "break-word", whiteSpace: "pre-wrap" }}>
<ResizableBox
className={classes.logs}
height={500}
width={500}
minConstraints={[250, 30]}
handle={
<span style={{ position: "absolute", right: "-10px", bottom: "-13px", cursor: "nw-resize" }}>
<ArrowForwardIosIcon color="primary" style={{ transform: "rotate(45deg)" }} />

@ -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;
}