merge dev

This commit is contained in:
phyzical 2022-04-23 10:26:51 +08:00
commit cdfbda1379
70 changed files with 2462 additions and 1876 deletions

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

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

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

4
dist/main.bundle.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

66
dist/vendor.bundle.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

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

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

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

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

@ -113,7 +113,7 @@ export const achievements: IMap<Achievement> = {
},
THE_COVENANT: {
...achievementData["THE_COVENANT"],
Icon: FactionNames.TheCovenant.toLowerCase(),
Icon: FactionNames.TheCovenant.toLowerCase().replace(/ /g, ""),
Condition: () => Player.factions.includes(FactionNames.TheCovenant),
},
[FactionNames.Illuminati.toUpperCase()]: {

@ -360,15 +360,15 @@ function generateStatsDescription(mults: IMap<number>, programs?: string[], star
desc = (
<>
{desc}
<br />+{f(mults.infiltration_base_rep_increase - 1)} Infiltration {FactionNames.Infiltrators} Reputation base
reward
<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.Infiltrators} Reputation reward
<br />+{f(mults.infiltration_rep_mult - 1)} Infiltration {FactionNames.ShadowsOfAnarchy} Reputation reward
</>
);
if (mults.infiltration_trade_mult)

@ -16,7 +16,7 @@ import {
initBladeburnerAugmentations,
initChurchOfTheMachineGodAugmentations,
initGeneralAugmentations,
initInfiltratorsAugmentations,
initSoAAugmentations,
initNeuroFluxGovernor,
initUnstableCircadianModulator,
} from "./data/AugmentationCreator";
@ -32,7 +32,7 @@ function createAugmentations(): void {
initNeuroFluxGovernor(),
initUnstableCircadianModulator(),
...initGeneralAugmentations(),
...initInfiltratorsAugmentations(),
...initSoAAugmentations(),
...(factionExists(FactionNames.Bladeburners) ? initBladeburnerAugmentations() : []),
...(factionExists(FactionNames.ChurchOfTheMachineGod) ? initChurchOfTheMachineGodAugmentations() : []),
].map(resetAugmentation);

@ -5,7 +5,6 @@ import { Programs } from "../../Programs/Programs";
import { WHRNG } from "../../Casino/RNG";
import React from "react";
import { FactionNames } from "../../Faction/data/FactionNames";
import { CityName } from "../../Locations/data/CityNames";
import { CONSTANTS } from "../../Constants";
function getRandomBonus(): any {
@ -96,159 +95,96 @@ function getRandomBonus(): any {
return bonuses[Math.floor(bonuses.length * randomNumber.random())];
}
export const initInfiltratorsAugmentations = (): Augmentation[] => [
export const initSoAAugmentations = (): Augmentation[] => [
new Augmentation({
name: AugmentationNames.BionicFingers,
repCost: 15e1,
name: AugmentationNames.WKSharmonizer,
repCost: 1e4,
moneyCost: 1e6,
infiltration_base_rep_increase: 5,
info:
"This state of the art augmentation removes the need for bones and tendons in your fingers, " +
"with this you will have the dexterity equal to the best rubik's cube player in the world. ",
factions: [FactionNames.Infiltrators],
`A copy of the WKS harmonizer from the MIA leader of the ${FactionNames.ShadowsOfAnarchy} ` +
"injects *Γ-based cells that provides general enhancement to the body.",
stats: (
<>
This augmentation makes many aspect of infiltration easier and more productive. Such as increased timer,
rewards, reduced damage taken, etc.
</>
),
factions: [FactionNames.ShadowsOfAnarchy],
}),
new Augmentation({
name: AugmentationNames.CorporationManagementImplant,
repCost: 25e1,
moneyCost: 1e6,
infiltration_rep_mult: 2.5,
info:
"As time went on corporations realized that managers were redundant if they could be replaced by AI chips " +
"implanted directly in the brain, and so the this implant was developed which could analyse the users brain " +
"to find the perfect tone and sounding voice to increase productivity of the user to maximum profits.",
factions: [FactionNames.Infiltrators],
}),
new Augmentation({
name: AugmentationNames.TranslationCircuit,
repCost: 5e2,
moneyCost: 1e6,
infiltration_trade_mult: 1.5,
info:
"A state of the art circuit module that manipulates the users voice to suit the needs of the situation, " +
"allowing the people listening to feel more persuaded by whats said due to the voice being auto translated " +
"to the listeners accent and language.",
factions: [FactionNames.Infiltrators],
}),
new Augmentation({
name: AugmentationNames.GoldenSuiteCase,
repCost: 5e2,
moneyCost: 1e6,
infiltration_sell_mult: 1.5,
info:
"Some say too much money is a curse, those people clearly have never owned a golden suitcase. it might be " +
"a bit heavier but it sure does make anything inside it look a lot more valuable.",
factions: [FactionNames.Infiltrators],
}),
new Augmentation({
name: AugmentationNames.TimeDilationInjection,
repCost: 5e2,
moneyCost: 1e6,
infiltration_timer_mult: 1.3,
info:
"Injected directly into the user eyes, as the serum seeps into the brain via the optic nerves perception " +
"of time begins to slow down allowing you to take more time to do things than the average person",
factions: [FactionNames.Infiltrators],
}),
new Augmentation({
name: AugmentationNames.BitaniumArmorAlloy,
repCost: 5e2,
moneyCost: 1e6,
infiltration_damage_reduction_mult: 0.7,
info:
`Deep in the mines of ${CityName.Ishima} miners found a strange new material, now known as bitanium after ` +
"many iterations of experimenting it was found to be exceptional at increasing ones amour at absorbing " +
"blunt damage when used as an alloy with almost any other metal you can think of.",
factions: [FactionNames.Infiltrators],
}),
new Augmentation({
name: AugmentationNames.PythiasBrainStem,
repCost: 1e2,
name: AugmentationNames.MightOfAres,
repCost: 1e4,
moneyCost: 1e6,
info:
"You found an old jar apparently containing the brain stem of one of the most famous " +
"fortune tellers in the world, installing this augmentation will apparently connect the synapses reactivating " +
"the magic this man once shared with the world ",
"Extra-occular neurons taken from old martial art master. Injecting the user the ability to " +
"predict enemy attack before they even know it themself.",
stats: (
<>This augmentation makes the Slash minigame easier by showing you via an indictor when the slash in coming.</>
),
factions: [FactionNames.Infiltrators],
factions: [FactionNames.ShadowsOfAnarchy],
}),
new Augmentation({
name: AugmentationNames.IntellisenseModule,
repCost: 1e2,
name: AugmentationNames.WisdomOfAthena,
repCost: 1e4,
moneyCost: 1e6,
info:
"A brain implant with AI power that focuses in auto linting and intellisense, which " +
"provides the ability to perform code completion better than any existing " +
"IDE environment on the market to date.",
info: "A connective brain implant to SASHA that focuses in pattern recognition and predictive templating.",
stats: <>This augmentation makes the Bracket minigame easier by removing all '[' ']'.</>,
factions: [FactionNames.Infiltrators],
factions: [FactionNames.ShadowsOfAnarchy],
}),
new Augmentation({
name: AugmentationNames.RearViewMirrorShoulderAttachment,
repCost: 1e2,
name: AugmentationNames.ChaosOfDionysus,
repCost: 1e4,
moneyCost: 1e6,
info: "Never again will you need to turn your head to see whats behind you.",
stats: <>This augmentation makes the Backwards minigame easier by making the words no longer backwards.</>,
factions: [FactionNames.Infiltrators],
info: "Opto-occipito implant to process visual signal before brain interpretation.",
stats: <>This augmentation makes the Backwards minigame easier by flipping the words.</>,
factions: [FactionNames.ShadowsOfAnarchy],
}),
new Augmentation({
name: AugmentationNames.KyberCrystalInjection,
repCost: 1e2,
name: AugmentationNames.BeautyOfAphrodite,
repCost: 1e4,
moneyCost: 1e6,
info:
"A weird looking shiny crystal which is crushed down and turned into a thick syrup, " +
"most people think its all homeopathic but there are a few that believe when injected people will " +
"believe what you say with one wave of the hand ",
"Pheromone extruder injected in the thoracodorsal nerve. Emits pleasing scent guaranteed to " +
"make conversational partners more agreeable.",
stats: <>This augmentation makes the Bribe minigame easier by indicating the incorrect paths.</>,
factions: [FactionNames.Infiltrators],
factions: [FactionNames.ShadowsOfAnarchy],
}),
new Augmentation({
name: AugmentationNames.DyslexiaModule,
repCost: 1e2,
name: AugmentationNames.TrickeryOfHermes,
repCost: 1e4,
moneyCost: 1e6,
info:
"A module initially developed to reverse the disability of dyslexia, But during human trials it was found " +
"to actually cause dyslexia, despite the fact scientists decided to sell it anyway. Who would want " +
"to install something like that... i guess it has the added benefit of qualifying for a disability card",
info: "Penta-dynamo-neurovascular-valve inserted in the carpal ligament, enhances dexterity.",
stats: <>This augmentation makes the Cheat Code minigame easier by allowing the opposite character.</>,
factions: [FactionNames.Infiltrators],
factions: [FactionNames.ShadowsOfAnarchy],
}),
new Augmentation({
name: AugmentationNames.CyberDecoder,
repCost: 1e2,
name: AugmentationNames.FloodOfPoseidon,
repCost: 1e4,
moneyCost: 1e6,
info:
"A cool looking do hickey that oddly resembles Keanu Reeves face, " +
"it has a usb cable that looks like it plugs into something.",
info: "Transtinatium VVD reticulator used in optico-sterbing recognition.",
stats: <>This augmentation makes the Symbol matching minigame easier by indicating the correct choice.</>,
factions: [FactionNames.Infiltrators],
factions: [FactionNames.ShadowsOfAnarchy],
}),
new Augmentation({
name: AugmentationNames.MineDetectionArmAttachment,
repCost: 1e2,
name: AugmentationNames.HuntOfArtemis,
repCost: 1e4,
moneyCost: 1e6,
info:
"You stumble across an old mine detection arm attachment at an army surplus store, " +
"on the side is inscribed 'X(' i wonder what happened to the original owner, " +
"its a bit beaten up but looks like it should still do the job.",
info: "magneto-turboencabulator based on technology by Micha Eike Siemon, increases the users electro-magnetic sensitivity.",
stats: (
<>
This augmentation makes the Minesweeper minigame easier by showing the location of all mines and keeping their
position.
</>
),
factions: [FactionNames.Infiltrators],
factions: [FactionNames.ShadowsOfAnarchy],
}),
new Augmentation({
name: AugmentationNames.SecurityWireContacts,
repCost: 1e2,
name: AugmentationNames.KnowledgeOfApollo,
repCost: 1e4,
moneyCost: 1e6,
info:
"This augment is a set of contacts for your eyes when installed allows for the user to see which" +
"wires are conducting security related information",
info: "Neodynic retention fjengeln spoofer using -φ karmions, net positive effect on implantees delta wave.",
stats: <>This augmentation makes the Wire Cutting minigame easier by indicating the incorrect wires.</>,
factions: [FactionNames.Infiltrators],
factions: [FactionNames.ShadowsOfAnarchy],
}),
];
@ -2063,7 +1999,8 @@ export function initNeuroFluxGovernor(): Augmentation {
stats: (
<>
This special augmentation can be leveled up infinitely. Each level of this augmentation increases MOST
multipliers by 1% (+{donationBonus * 100}% boosted by real life blood donations), stacking multiplicatively.
multipliers by 1% (+{(donationBonus * 100).toFixed(6)}% boosted by real life blood donations), stacking
multiplicatively.
</>
),
hacking_chance_mult: 1.01 + donationBonus,
@ -2094,7 +2031,7 @@ export function initNeuroFluxGovernor(): Augmentation {
work_money_mult: 1.01 + donationBonus,
factions: Object.values(FactionNames).filter(
(factionName) =>
![FactionNames.Infiltrators, FactionNames.Bladeburners, FactionNames.ChurchOfTheMachineGod].includes(
![FactionNames.ShadowsOfAnarchy, FactionNames.Bladeburners, FactionNames.ChurchOfTheMachineGod].includes(
factionName,
),
),

@ -114,22 +114,27 @@ export enum AugmentationNames {
StaneksGift2 = "Stanek's Gift - Awakening",
StaneksGift3 = "Stanek's Gift - Serenity",
/*
MightOfAres = "Might of Ares", // slash
WisdomOfAthena = "Wisdom of Athena", // bracket
TrickeryOfHermes = "Trickery of Hermes", // cheatcode
BeautyOfAphrodite = "Beauty of Aphrodite", // bribe
ChaosOfDionysus = "Chaos of Dionysus", // reverse
FloodOfPoseidon = "Flood of Poseidon", // hex
HuntOfArtemis = "Hunt of Artemis", // mine
KnowledgeOfApollo = "Knowledge of Apollo", // wire
*/
// Infiltrators MiniGames
PythiasBrainStem = "Pythia's Brain Stem",
IntellisenseModule = "Intellisense Module",
DyslexiaModule = "Dyslexia Module",
KyberCrystalInjection = "Kyber Crystal Injection",
RearViewMirrorShoulderAttachment = "Rear View Mirror Shoulder Attachment",
CyberDecoder = "Cyber Decoder",
MineDetectionArmAttachment = "Mine Detection Arm Attachment",
SecurityWireContacts = "Security Wire Contacts",
// Infiltrators general
BionicFingers = "Bionic Fingers",
CorporationManagementImplant = "Corporation Management Implant",
TranslationCircuit = "Translation Circuit",
GoldenSuiteCase = "Golden Suite Case",
TimeDilationInjection = "Time Dilation Injection",
BitaniumArmorAlloy = "Bitanium Armor Alloy",
MightOfAres = "SoA - Might of Ares", // slash
WisdomOfAthena = "SoA - Wisdom of Athena", // bracket
TrickeryOfHermes = "SoA - Trickery of Hermes", // cheatcode
BeautyOfAphrodite = "SoA - Beauty of Aphrodite", // bribe
ChaosOfDionysus = "SoA - Chaos of Dionysus", // reverse
FloodOfPoseidon = "SoA - Flood of Poseidon", // hex
HuntOfArtemis = "SoA - Hunt of Artemis", // mine
KnowledgeOfApollo = "SoA - Knowledge of Apollo", // wire
WKSharmonizer = "SoA - phyzical WKS harmonizer",
//Wasteland Augs
//PepBoy: "P.E.P-Boy", Plasma Energy Projection System

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

@ -254,41 +254,6 @@ export function PlayerMultipliers(): React.ReactElement {
);
}
rightColData.push(
...[
[
"Infiltrator Rep reward",
Player.infiltration_rep_mult,
Player.infiltration_rep_mult * mults.infiltration_rep_mult,
1,
],
[
"Infiltration sell",
Player.infiltration_sell_mult,
Player.infiltration_sell_mult * mults.infiltration_sell_mult,
1,
],
[
"Infiltration trade",
Player.infiltration_trade_mult,
Player.infiltration_trade_mult * mults.infiltration_trade_mult,
1,
],
[
"Infiltration minigame timer",
Player.infiltration_timer_mult,
Player.infiltration_timer_mult * mults.infiltration_timer_mult,
1,
],
[
"Infiltration minigame damage reduction",
Player.infiltration_damage_reduction_mult,
-1 * (1 - Player.infiltration_damage_reduction_mult * mults.infiltration_damage_reduction_mult),
1,
],
].map((data): MultiplierListItemData => (data as any).concat([Settings.theme.primary])),
);
const hasLeftImprovements = +!!(leftColData.filter((item) => item[2] !== 0).length > 0),
hasRightImprovements = +!!(rightColData.filter((item) => item[2] !== 0).length > 0);

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

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

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

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

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

@ -3,6 +3,7 @@ import { IMap } from "../types";
import { FactionNames } from "./data/FactionNames";
import { use } from "../ui/Context";
import { Option } from "./ui/Option";
import { Typography } from "@mui/material";
interface FactionInfoParams {
infoText?: JSX.Element;
@ -511,51 +512,17 @@ export const FactionInfos: IMap<FactionInfo> = {
);
},
}),
// prettier-ignore
[FactionNames.Infiltrators]: new FactionInfo({
infoText:(<>
{".,=,.==.²=.'=,.=░.==.:=,,▄▓▓▓▓▓▓▓▓▓╬░.==.²!.,=,.=⌐.==.!=,.=,.==.²=.,=,.==.²=.!=,"}<br />
{"'¡»⌐^»∩^:»^:=^`=∩':+]▄▄▓▓▓▓▓▓▓▓▓▓▓▓▓▒^»∩^»=^:»^²»⌐^»∩^:=^`»^`»∩^:»^¡»^`»⌐'»÷'\»'"}<br />
{"░░_|░_'░⌐_░∩`|▒▄▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░░`,░``░∩`»░_|░¬`░⌐_░░`'PHYZ|C@L`»░_!░⌐`░░_|"}<br />
{".,=,.»\.²=,,[▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓█▓▒╦╔░¡_²².,=,,=:.==_!²,,=,_==,W@Z,=,,=\.!=./²,"}<br />
{"'`»^^»∩':»')▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓████▒▐▒Ü▒^»=^¡»^^»⌐'»∩':»^`»^'=^:=^H3R3^»∩^:»':=^"}<br />
{"░∩`:░_'░⌐`░▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓█████ÑÜP╩Ü╙░`_░∩`|░`:░_`░∩`|∩`:░_`░⌐_░░`|░`,░⌐`░∩`»"}<br />
{".,=,,==_²=,▀▓▓▓▓▓▓▓▓▓▓▓▓▓▓██▌Ñ▓██ÑÜÜ▒Ü▒░_»=,,=,.==_»=,!=,,=,_==,»░,,=,,==_»=,!=,"}<br />
{"'`=^^=∩^:»^:»└'µ╫█████████╬▓▓▓██Ññ▒NÜÜ▒╠^»=':=^^=∩^»=^:=^`=⌐^»»':»`¡»^`=∩^:=^\»'"}<br />
{"░░`:░_,░⌐`=∩`|░_¡█╬▀███▓╬╬▀▓▓▀Ñ▒ÄÜ╟ñÜÜ╠D░¬_░░_|=_!░_`░⌐`=∩_:░_'░⌐_░░`!=`┌░__░∩_»"}<br />
{".,=,.==.!=,,=,.=░╠████Ñ▒▒ÄÜñÜÜÖ╠╩Ü║Üû╠╙».»░.:=,.=⌐.==.!=,,=,.==.!=.,=,_==.»=.!=,"}<br />
{"^`»^`»∩`:»'\»^^»⌐^╚╬▒Ü▒▒▒ÜÜ▒╚╩▒▒RÖÜ╠╠▒∩∩';╓';»^'»⌐`»»^:»^^»⌐^»∩':»^¡»''»∩`:»^\»^"}<br />
{"░░ :░ '░⌐_░░_|░`!░`╙▒▒╠╠ÜÉ▒╚Ö╠╠╠╠╟╬╠╠╠╬╬▓▓█▓▓▓▓▓▌▄▒,,░⌐_|░_:░`,░⌐`░░ |░ '░_`░∩_|"}<br />
{".,=,.=».!=,,=,.=░.==.└╙╙╜╚╩╩╙╙Ü7┘,░╚╬╠╬▓████▓▓▓▓▓█▓▓▓▓▓▓▓▓▓▓▒==,²».,»,,=░.!=.!=,"}<br />
{"'^»⌐^»∩^:÷^\»^^»⌐'»»^:»^^»⌐^»∩^:»'`║██████████████▓████████▓▓m»'»»^¡»^^»∩^»»^\»^"}<br />
{"░∩`!░_'░⌐`░░`|░,;░__░⌐`░░`|░-'░⌐`░µ▓█████████████▓▓▓▓▀▀╫▓███▓▌`░⌐_░░`|░`!░⌐`░∩`|"}<br />
{"_,=,_²».:=:▒▒╠▒▒╠╠▒▒,:=,,=⌐_»░_:=,▓██████████████▓▓▓▓▒!=,███▓▓░_»=_,=,_=░_!░.'=,"}<br />
{"▒╙R▓▓▄▄╣▓▓▓▓▓▓▓▓▓╬╬╠▒╠▄▄▄µ∩'»=]▄▄▓████Ñ»╙▀███████▓▓▓▓Ñ:»^╫█▓▓▓H`:='`=^`=∩`»=`\=^"}<br />
{"Ü⌐»╙▓▓▓▓▓▓╬▌╠╬▒╬╬╬╠╠Ö╣▓▓▓▓▓▓▓▓██▓███▀U``░``╫███████▓▓Ñ⌐_=╫█▓▓▓Ñ░_`░░_|░_┌░``░░_»"}<br />
{"|░::╟▓▓▀╚╙░/=,,╙░░╙░,⌡╩▀▓█████████▓╙,,==_²²╫███████▓▓░!=,╙╬╩▒▒▒╦╦░,,=,_=\.²=.!=,"}<br />
{"^'ܵÅÑ»`:='\=^^»⌐^»=':÷^^=^┴╚╢▀▀▀╙`»^'=∩`:»║██████▓▓▌^:»'╔╠▒▒▒▒▒▒▒╠M≡╦`»⌐'»=^╒»`"}<br />
{"░░_|░_'░⌐`░░_»░`!░¬`░▄▄▄▌▄▄▄▄▄▄╥,░░_:░`,░,╫███████▓▓Ü░⌐`=╠▒▒▒▒▒▒╠╠╩░`»╝╦:░_`░∩`»"}<br />
{"_,»,_!\_!=,,=,_»░_²1████████████████▓▄▓▓▄▓█████▓▓▓▓Ñ»_!=,7╝╬╚╝╩╩░ù,,»,,╫▒,²»_!=,"}<br />
{"'¡»^^»∩^:÷^\»^^»⌐^»=████████████████████████████▓▓Ñ»∩':»^^»╬^÷∩^:»^¡÷^║╣ÜR≈»^\÷^"}<br />
{"░░`|░¬'░⌐`░░`|░`:░``╫███████▀▀██████████████████▓▀░¬`░⌐`|░`╠╣╣░▒░_▒╥║╬╣▒╙Ü^=░░`|"}<br />
{"_,=,_»░_²=,,=,_=░_==|██████░==_╙╩▀██████████████Ñ⌐_!=_:=,_░╟%╩╬╟╣╣╣╬Ñ╣Φ╬»_²=┐╟╣╓"}<br />
{"`¡»^^»∩^»»^\»^^»⌐^»»[█████▓^»∩'»:^)▓██████████▀┴=⌐`»='»»^`»╠^»╠╬╠n╬╬▒^[╠∩^»»^:=^"}<br />
{"░∩`:░_'░⌐`░∩_»░_:░¬_▐██████▒``░⌐`▄█████████▓Ñ`»░`!░_`░⌐_|∩|╬░``╠⌐_░╠_»░╚Ü░_`░⌐`|"}<br />
{",,=,,==_²=.,=,,=░,==╫██████▒==,²▓███████▓╨░,,=,_=⌐,=»,!=,,=╠░»=╬==.╠Ü,_=╢,!=,!=,"}<br />
{"^¡÷^^»∩^:='\»^^»⌐^»»███████^»»^╫██████▄┐':»':=^^»⌐`»»^\=^░╬Ü'»┐╬░»''╢^^»∩Φ░»^\»^"}<br />
{"░=`!░`¡░⌐`░░`|░`:░,███████▓░_`▓███████▌'░¬`░░`|░ ¡░``░⌐`|╝╨|░``╬H_░û╬▒░`:╟╬^░∩_»"}<br />
{",_=,,»»_!»,,░╓▄▄▓▓█████████,=»,╙▀███████▌▄».,=,_=⌐_»»_²=,,=,,²=╚ÑÜ,,╚Ñ_=»_!=.!»_"}<br />
{"^^»^^»∩^:Φ███████████████▌╜^»»':»^¡╬▓██████▄░»^^»⌐'»»':»^^»''»»^»»^¡»^^»⌐':»'\»^"}<br />
{"░∩`:░`'░⌐╨████████████████:░``░⌐`░░`|░▀██████▓▄░`:░_`░░`»░`:░``░⌐`░░`|░`,░⌐`░░`|"}<br />
{"_,=,,==_!=,,=,_»=_==_²ù││ù,_==_!=,,=,,=░████████▄░_»»_!=,_=░_==_»»_,=,,=░_»=,'»,"}<br />
{"^¡»^^»∩^:=^\=^^»⌐^»»^:»^^=^^»∩^:=^`='^;▄▓████████▌^»»^:=^`»⌐^»»^»»^¡»^^»∩^:=^\»^"}<br />
{"░∩`!=_'░⌐`░∩`|░`!░``░⌐`|░`|░`'░⌐`░µ▄▓███████████▄¡░_`░⌐`|░`!░_`░⌐`░∩`|░_¡░⌐`░░`|"}<br />
{"################################################################################"}<br /><br />
Experts in getting in and out in plain sight<br /><br />
Some some speculate that the oceans movies are documentaries based on this group<br /><br />
Some members are even rumored to be able to pick locks like the fonz, one hit and it will swing open ayeeeee<br /><br />
Note that for this faction, reputation can only be gained by performing infiltration tasks</>),
special: true,
keepOnInstall: true,
}),
[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,5 +32,5 @@ export enum FactionNames {
CyberSec = "CyberSec",
Bladeburners = "Bladeburners",
ChurchOfTheMachineGod = "Church of the Machine God",
Infiltrators = "The Infiltrators",
ShadowsOfAnarchy = "Shadows of Anarchy",
}

@ -167,7 +167,7 @@ export function AugmentationsPage(props: IProps): React.ReactElement {
);
}
const multiplierComponent =
props.faction.name !== FactionNames.Infiltrators ? (
props.faction.name !== FactionNames.ShadowsOfAnarchy ? (
<Typography>
Price multiplier: x {numeralWrapper.formatMultiplier(getGenericAugmentationPriceMultiplier())}
</Typography>

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

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

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

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

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

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

@ -1,6 +1,8 @@
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,
@ -15,7 +17,7 @@ export function calculateSellInformationCashReward(
Math.pow(difficulty, 3) *
3e3 *
levelBonus *
player.infiltration_sell_mult *
(player.hasAugmentation(AugmentationNames.WKSharmonizer) ? 1.5 : 1) *
BitNodeMultipliers.InfiltrationMoney
);
}
@ -33,17 +35,17 @@ export function calculateTradeInformationRepReward(
Math.pow(difficulty, 3) *
3e3 *
levelBonus *
player.infiltration_sell_mult *
(player.hasAugmentation(AugmentationNames.WKSharmonizer) ? 1.5 : 1) *
BitNodeMultipliers.InfiltrationMoney
);
}
export function calculateInfiltratorsRepReward(player: IPlayer, difficulty: number): number {
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) * 10;
const baseRepGain = (difficulty / maxStartingSecurityLevel) * 5000;
return (baseRepGain + player.infiltration_base_rep_increase) * player.infiltration_rep_mult;
return baseRepGain * (player.hasAugmentation(AugmentationNames.WKSharmonizer) ? 2 : 1) * (1 + faction.favor / 100);
}

@ -36,7 +36,7 @@ export function BackwardGame(props: IMinigameProps): React.ReactElement {
const timer = difficulty.timer;
const [answer] = useState(makeAnswer(difficulty));
const [guess, setGuess] = useState("");
const hasAugment = Player.hasAugmentation(AugmentationNames.RearViewMirrorShoulderAttachment, true);
const hasAugment = Player.hasAugmentation(AugmentationNames.ChaosOfDionysus, true);
function press(this: Document, event: KeyboardEvent): void {
event.preventDefault();

@ -33,7 +33,7 @@ const difficulties: {
function generateLeftSide(difficulty: Difficulty): string {
let str = "";
const options = [KEY.OPEN_BRACKET, KEY.LESS_THAN, KEY.OPEN_PARENTHESIS, KEY.OPEN_BRACE];
if (Player.hasAugmentation(AugmentationNames.IntellisenseModule, true)) {
if (Player.hasAugmentation(AugmentationNames.WisdomOfAthena, true)) {
options.splice(0, 1);
}
const length = random(difficulty.min, difficulty.max);

@ -47,7 +47,7 @@ export function BribeGame(props: IMinigameProps): React.ReactElement {
let upColor = defaultColor;
let downColor = defaultColor;
let choiceColor = defaultColor;
const hasAugment = Player.hasAugmentation(AugmentationNames.KyberCrystalInjection, true);
const hasAugment = Player.hasAugmentation(AugmentationNames.BeautyOfAphrodite, true);
if (hasAugment) {
const upIndex = index + 1 >= choices.length ? 0 : index + 1;

@ -42,7 +42,7 @@ export function CheatCodeGame(props: IMinigameProps): React.ReactElement {
const timer = difficulty.timer;
const [code] = useState(generateCode(difficulty));
const [index, setIndex] = useState(0);
const hasAugment = Player.hasAugmentation(AugmentationNames.DyslexiaModule, true);
const hasAugment = Player.hasAugmentation(AugmentationNames.TrickeryOfHermes, true);
function press(this: Document, event: KeyboardEvent): void {
event.preventDefault();

@ -40,7 +40,7 @@ export function Cyberpunk2077Game(props: IMinigameProps): React.ReactElement {
const [currentAnswerIndex, setCurrentAnswerIndex] = useState(0);
const [pos, setPos] = useState([0, 0]);
const hasAugment = Player.hasAugmentation(AugmentationNames.CyberDecoder, true);
const hasAugment = Player.hasAugmentation(AugmentationNames.FloodOfPoseidon, true);
function press(this: Document, event: KeyboardEvent): void {
event.preventDefault();
const move = [0, 0];

@ -13,6 +13,7 @@ import { MinesweeperGame } from "./MinesweeperGame";
import { WireCuttingGame } from "./WireCuttingGame";
import { Victory } from "./Victory";
import Typography from "@mui/material/Typography";
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
interface IProps {
StartingDifficulty: number;
@ -93,7 +94,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.infiltration_damage_reduction_mult;
: props.StartingDifficulty * 3 * (player.hasAugmentation(AugmentationNames.WKSharmonizer) ? 0.5 : 1);
if (player.takeDamage(damage)) {
router.toCity();
return;
@ -112,7 +113,16 @@ export function Game(props: IProps): React.ReactElement {
stageComponent = <Countdown onFinish={() => setStage(Stage.Minigame)} />;
break;
case Stage.Minigame: {
const MiniGame = minigames[gameIds.id];
/**
*
BackwardGame,
BribeGame,
CheatCodeGame,
Cyberpunk2077Game,
MinesweeperGame,
WireCuttingGame,
*/
const MiniGame = WireCuttingGame; // minigames[gameIds.id];
stageComponent = <MiniGame onSuccess={success} onFailure={failure} difficulty={props.Difficulty + level / 50} />;
break;
}

@ -4,6 +4,7 @@ import withStyles from "@mui/styles/withStyles";
import { Theme } from "@mui/material/styles";
import Grid from "@mui/material/Grid";
import { use } from "../../ui/Context";
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
const TimerProgress = withStyles((theme: Theme) => ({
root: {
@ -23,7 +24,7 @@ interface IProps {
export function GameTimer(props: IProps): React.ReactElement {
const player = use.Player();
const [v, setV] = useState(100);
const totalMillis = player.infiltration_timer_mult * props.millis;
const totalMillis = (player.hasAugmentation(AugmentationNames.WKSharmonizer) ? 1.3 : 1) * props.millis;
const tick = 200;
useEffect(() => {

@ -38,7 +38,7 @@ export function MinesweeperGame(props: IMinigameProps): React.ReactElement {
const [answer, setAnswer] = useState(generateEmptyField(difficulty));
const [pos, setPos] = useState([0, 0]);
const [memoryPhase, setMemoryPhase] = useState(true);
const hasAugment = Player.hasAugmentation(AugmentationNames.MineDetectionArmAttachment, true);
const hasAugment = Player.hasAugmentation(AugmentationNames.HuntOfArtemis, true);
function press(this: Document, event: KeyboardEvent): void {
event.preventDefault();
if (memoryPhase) return;

@ -40,7 +40,7 @@ export function SlashGame(props: IMinigameProps): React.ReactElement {
props.onSuccess();
}
}
const hasAugment = Player.hasAugmentation(AugmentationNames.PythiasBrainStem, true);
const hasAugment = Player.hasAugmentation(AugmentationNames.MightOfAres, true);
const phaseZeroTime = Math.random() * 3250 + 1500 - (250 + difficulty.window);
const phaseOneTime = 250;
const timeUntilAttacking = phaseZeroTime + phaseOneTime;

@ -15,6 +15,7 @@ import {
calculateSellInformationCashReward,
calculateTradeInformationRepReward,
} from "../formulas/victory";
import { inviteToFaction } from "../../Faction/FactionHelpers";
interface IProps {
StartingDifficulty: number;
@ -33,12 +34,12 @@ export function Victory(props: IProps): React.ReactElement {
router.toCity();
}
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, props.StartingDifficulty);
const infiltrationRepGain = calculateInfiltratorsRepReward(player, soa, props.StartingDifficulty);
const infiltratorFaction = Factions[FactionNames.Infiltrators];
const isMemberOfInfiltrators = infiltratorFaction && infiltratorFaction.isMember;
const isMemberOfInfiltrators = player.factions.includes(FactionNames.ShadowsOfAnarchy);
function sell(): void {
handleInfiltrators();
@ -58,9 +59,9 @@ export function Victory(props: IProps): React.ReactElement {
}
function handleInfiltrators(): void {
player.hasCompletedAnInfiltration = true;
inviteToFaction(Factions[FactionNames.ShadowsOfAnarchy]);
if (isMemberOfInfiltrators) {
infiltratorFaction.playerReputation += infiltrationRepGain;
soa.playerReputation += infiltrationRepGain;
}
}
@ -75,7 +76,7 @@ export function Victory(props: IProps): React.ReactElement {
You{" "}
{isMemberOfInfiltrators ? (
<>
have gained {formatNumber(infiltrationRepGain, 2)} rep for {FactionNames.Infiltrators} and{" "}
have gained {formatNumber(infiltrationRepGain, 2)} rep for {FactionNames.ShadowsOfAnarchy} and{" "}
</>
) : (
<></>

@ -64,10 +64,10 @@ export function WireCuttingGame(props: IMinigameProps): React.ReactElement {
const [wires] = useState(generateWires(difficulty));
const [cutWires, setCutWires] = useState(new Array(wires.length).fill(false));
const [questions] = useState(generateQuestion(wires, difficulty));
const hasAugment = Player.hasAugmentation(AugmentationNames.SecurityWireContacts, true);
const hasAugment = Player.hasAugmentation(AugmentationNames.KnowledgeOfApollo, true);
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(() => {
@ -110,9 +110,15 @@ export function WireCuttingGame(props: IMinigameProps): React.ReactElement {
<Typography key={i}>{question.toString()}</Typography>
))}
<Typography>
{new Array(wires.length).fill(0).map((_, i) => (
<span key={i}>&nbsp;{i + 1}&nbsp;&nbsp;&nbsp;&nbsp;</span>
))}
{new Array(wires.length).fill(0).map((_, i) => {
const isCorrectWire = checkWire(i + 1);
const color = hasAugment && !isCorrectWire ? Settings.theme.disabled : Settings.theme.primary;
return (
<span key={i} style={{ color: color }}>
&nbsp;{i + 1}&nbsp;&nbsp;&nbsp;&nbsp;
</span>
);
})}
</Typography>
{new Array(8).fill(0).map((_, i) => (
<div key={i}>

@ -70,11 +70,11 @@ export const RamCostConstants: IMap<number> = {
ScriptStanekPlace: 5,
ScriptStanekFragmentAt: 2,
ScriptStanekDeleteAt: 0.15,
ScriptInfiltrationCalculateDifficulty: 2.5,
ScriptInfiltrationCalculateRewards: 2.5,
ScriptInfiltrationGetLocations: 5,
ScriptInfiltrationGetInfiltrations: 15,
ScriptStanekAcceptGift: 2,
};
function SF4Cost(cost: number): (player: IPlayer) => number {
@ -296,6 +296,7 @@ const stanek: IMap<any> = {
placeFragment: RamCostConstants.ScriptStanekPlace,
getFragment: RamCostConstants.ScriptStanekFragmentAt,
removeFragment: RamCostConstants.ScriptStanekDeleteAt,
acceptGift: RamCostConstants.ScriptStanekAcceptGift,
};
// UI API

@ -1,3 +1,4 @@
import $ from "jquery";
import { vsprintf, sprintf } from "sprintf-js";
import { getRamCost } from "./Netscript/RamCostGenerator";
@ -527,7 +528,8 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
const sleeve = NetscriptSleeve(Player, workerScript, helper);
const extra = NetscriptExtra(Player, workerScript, helper);
const hacknet = NetscriptHacknet(Player, workerScript, helper);
const infiltration = NetscriptInfiltration(Player, workerScript, helper);
const infiltration = wrapAPI(helper, {}, workerScript, NetscriptInfiltration(Player), "infiltration")
.infiltration as unknown as IInfiltration;
const stanek = wrapAPI(helper, {}, workerScript, NetscriptStanek(Player, workerScript, helper), "stanek")
.stanek as unknown as IStanek;
const bladeburner = NetscriptBladeburner(Player, workerScript, helper);
@ -2491,12 +2493,6 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
bladeburner_stamina_gain_mult: Player.bladeburner_stamina_gain_mult,
bladeburner_analysis_mult: Player.bladeburner_analysis_mult,
bladeburner_success_chance_mult: Player.bladeburner_success_chance_mult,
infiltration_base_rep_increase: Player.infiltration_base_rep_increase,
infiltration_rep_mult: Player.infiltration_rep_mult,
infiltration_trade_mult: Player.infiltration_trade_mult,
infiltration_sell_mult: Player.infiltration_sell_mult,
infiltration_timer_mult: Player.infiltration_timer_mult,
infiltration_damage_reduction_mult: Player.infiltration_damage_reduction_mult,
bitNodeN: Player.bitNodeN,
totalPlaytime: Player.totalPlaytime,
playtimeSinceLastAug: Player.playtimeSinceLastAug,

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

@ -1,13 +1,6 @@
import { INetscriptHelper } from "./INetscriptHelper";
import { IPlayer } from "../PersonObjects/IPlayer";
import { WorkerScript } from "../Netscript/WorkerScript";
import { getRamCost } from "../Netscript/RamCostGenerator";
import {
Infiltration as IInfiltration,
InfiltrationLocation,
InfiltrationReward,
} from "../ScriptEditor/NetscriptDefinitions";
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";
@ -16,28 +9,23 @@ import {
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,
workerScript: WorkerScript,
helper: INetscriptHelper,
): IInfiltration {
export function NetscriptInfiltration(player: IPlayer): InternalAPI<IInfiltration> {
const getLocationsWithInfiltrations = Object.values(Locations).filter(
(location: Location) => location.infiltrationData,
);
const calculateInfiltrationData = (location: Location | undefined): InfiltrationLocation => {
if (location === undefined)
throw helper.makeRuntimeErrorMsg(
`infiltration.calculateReward`,
"The provided location does not exist or does not provide infiltrations",
);
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 helper.makeRuntimeErrorMsg(
`infiltration.calculateReward`,
"The provided location does not exist or does not provide infiltrations",
);
throw ctx.makeRuntimeErrorMsg(`Location '${location}' does not provide infiltrations.`);
const startingSecurityLevel = location.infiltrationData.startingSecurityLevel;
const difficulty = calculateDifficulty(player, startingSecurityLevel);
const reward = calculateReward(player, startingSecurityLevel);
@ -47,29 +35,20 @@ export function NetscriptInfiltration(
reward: {
tradeRep: calculateTradeInformationRepReward(player, reward, maxLevel, difficulty),
sellCash: calculateSellInformationCashReward(player, reward, maxLevel, difficulty),
infiltratorRep: calculateInfiltratorsRepReward(player, difficulty),
SoARep: calculateInfiltratorsRepReward(player, Factions[FactionNames.ShadowsOfAnarchy], difficulty),
},
difficulty: difficulty,
};
};
return {
calculateDifficulty: function (locationName: string): number {
const location = getLocationsWithInfiltrations.find((infilLocation) => infilLocation.name === locationName);
helper.updateDynamicRam("calculateDifficulty", getRamCost(player, "infiltration", "calculateDifficulty"));
return calculateInfiltrationData(location).difficulty;
},
calculateRewards: function (locationName: string): InfiltrationReward {
const location = getLocationsWithInfiltrations.find((infilLocation) => infilLocation.name === locationName);
helper.updateDynamicRam("calculateReward", getRamCost(player, "infiltration", "calculateReward"));
return calculateInfiltrationData(location).reward;
},
getLocations: function (): Location[] {
helper.updateDynamicRam("getLocations", getRamCost(player, "infiltration", "getLocations"));
return getLocationsWithInfiltrations;
},
getInfiltrations: function (): InfiltrationLocation[] {
helper.updateDynamicRam("getInfiltrations", getRamCost(player, "infiltration", "getInfiltrations"));
return getLocationsWithInfiltrations.map(calculateInfiltrationData);
getPossibleLocations: () => (): string[] => {
return getLocationsWithInfiltrations.map((l) => l + "");
},
getInfiltration:
(ctx: NetscriptContext) =>
(_location: unknown): InfiltrationLocation => {
const location = ctx.helper.string("location", _location);
return calculateInfiltrationData(ctx, location);
},
};
}

@ -164,10 +164,7 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
const augs = getFactionAugmentationsFiltered(player, fac);
if (!augs.includes(augName)) {
workerScript.log(
"purchaseAugmentation",
() => `Faction '${facName}' does not have the '${augName}' augmentation.`,
);
_ctx.log(() => `Faction '${facName}' does not have the '${augName}' augmentation.`);
return false;
}
@ -175,25 +172,25 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
if (!isNeuroflux) {
for (let j = 0; j < player.queuedAugmentations.length; ++j) {
if (player.queuedAugmentations[j].name === aug.name) {
workerScript.log("purchaseAugmentation", () => `You already have the '${augName}' augmentation.`);
_ctx.log(() => `You already have the '${augName}' augmentation.`);
return false;
}
}
for (let j = 0; j < player.augmentations.length; ++j) {
if (player.augmentations[j].name === aug.name) {
workerScript.log("purchaseAugmentation", () => `You already have the '${augName}' augmentation.`);
_ctx.log(() => `You already have the '${augName}' augmentation.`);
return false;
}
}
}
if (fac.playerReputation < aug.getCost(player).repCost) {
workerScript.log("purchaseAugmentation", () => `You do not have enough reputation with '${fac.name}'.`);
_ctx.log(() => `You do not have enough reputation with '${fac.name}'.`);
return false;
}
const res = purchaseAugmentation(aug, fac, true);
workerScript.log("purchaseAugmentation", () => res);
_ctx.log(() => res);
if (isString(res) && res.startsWith("You purchased")) {
player.gainIntelligenceExp(CONSTANTS.IntelligenceSingFnBaseExpGain * 10);
return true;
@ -206,7 +203,7 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
_ctx.helper.checkSingularityAccess();
const cbScript = _ctx.helper.string("cbScript", _cbScript);
workerScript.log("softReset", () => "Soft resetting. This will cause this script to be killed");
_ctx.log(() => "Soft resetting. This will cause this script to be killed");
setTimeout(() => {
installAugmentations(true);
runAfterReset(cbScript);
@ -222,14 +219,11 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
const cbScript = _ctx.helper.string("cbScript", _cbScript);
if (player.queuedAugmentations.length === 0) {
workerScript.log("installAugmentations", () => "You do not have any Augmentations to be installed.");
_ctx.log(() => "You do not have any Augmentations to be installed.");
return false;
}
player.gainIntelligenceExp(CONSTANTS.IntelligenceSingFnBaseExpGain * 10);
workerScript.log(
"installAugmentations",
() => "Installing Augmentations. This will cause this script to be killed",
);
_ctx.log(() => "Installing Augmentations. This will cause this script to be killed");
setTimeout(() => {
installAugmentations();
runAfterReset(cbScript);
@ -246,11 +240,11 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
const locationName = _ctx.helper.string("locationName", _locationName);
const location = Object.values(Locations).find((l) => l.name === locationName);
if (!location) {
workerScript.log("goToLocation", () => `No location named ${locationName}`);
_ctx.log(() => `No location named ${locationName}`);
return false;
}
if (player.city !== location.city) {
workerScript.log("goToLocation", () => `No location named ${locationName} in ${player.city}`);
_ctx.log(() => `No location named ${locationName} in ${player.city}`);
return false;
}
Router.toLocation(location);
@ -266,17 +260,14 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
const wasFocusing = player.focus;
if (player.isWorking) {
const txt = player.singularityStopWork();
workerScript.log("universityCourse", () => txt);
_ctx.log(() => txt);
}
let costMult, expMult;
switch (universityName.toLowerCase()) {
case LocationName.AevumSummitUniversity.toLowerCase():
if (player.city != CityName.Aevum) {
workerScript.log(
"universityCourse",
() => `You cannot study at 'Summit University' because you are not in '${CityName.Aevum}'.`,
);
_ctx.log(() => `You cannot study at 'Summit University' because you are not in '${CityName.Aevum}'.`);
return false;
}
player.gotoLocation(LocationName.AevumSummitUniversity);
@ -285,10 +276,7 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
break;
case LocationName.Sector12RothmanUniversity.toLowerCase():
if (player.city != CityName.Sector12) {
workerScript.log(
"universityCourse",
() => `You cannot study at 'Rothman University' because you are not in '${CityName.Sector12}'.`,
);
_ctx.log(() => `You cannot study at 'Rothman University' because you are not in '${CityName.Sector12}'.`);
return false;
}
player.location = LocationName.Sector12RothmanUniversity;
@ -297,8 +285,7 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
break;
case LocationName.VolhavenZBInstituteOfTechnology.toLowerCase():
if (player.city != CityName.Volhaven) {
workerScript.log(
"universityCourse",
_ctx.log(
() => `You cannot study at 'ZB Institute of Technology' because you are not in '${CityName.Volhaven}'.`,
);
return false;
@ -308,7 +295,7 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
expMult = 4;
break;
default:
workerScript.log("universityCourse", () => `Invalid university name: '${universityName}'.`);
_ctx.log(() => `Invalid university name: '${universityName}'.`);
return false;
}
@ -333,7 +320,7 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
task = CONSTANTS.ClassLeadership;
break;
default:
workerScript.log("universityCourse", () => `Invalid class name: ${className}.`);
_ctx.log(() => `Invalid class name: ${className}.`);
return false;
}
player.startClass(costMult, expMult, task);
@ -344,7 +331,7 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
player.stopFocusing();
Router.toTerminal();
}
workerScript.log("universityCourse", () => `Started ${task} at ${universityName}`);
_ctx.log(() => `Started ${task} at ${universityName}`);
return true;
},
@ -357,14 +344,13 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
const wasFocusing = player.focus;
if (player.isWorking) {
const txt = player.singularityStopWork();
workerScript.log("gymWorkout", () => txt);
_ctx.log(() => txt);
}
let costMult, expMult;
switch (gymName.toLowerCase()) {
case LocationName.AevumCrushFitnessGym.toLowerCase():
if (player.city != CityName.Aevum) {
workerScript.log(
"gymWorkout",
_ctx.log(
() =>
`You cannot workout at '${LocationName.AevumCrushFitnessGym}' because you are not in '${CityName.Aevum}'.`,
);
@ -376,8 +362,7 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
break;
case LocationName.AevumSnapFitnessGym.toLowerCase():
if (player.city != CityName.Aevum) {
workerScript.log(
"gymWorkout",
_ctx.log(
() =>
`You cannot workout at '${LocationName.AevumSnapFitnessGym}' because you are not in '${CityName.Aevum}'.`,
);
@ -389,8 +374,7 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
break;
case LocationName.Sector12IronGym.toLowerCase():
if (player.city != CityName.Sector12) {
workerScript.log(
"gymWorkout",
_ctx.log(
() =>
`You cannot workout at '${LocationName.Sector12IronGym}' because you are not in '${CityName.Sector12}'.`,
);
@ -402,8 +386,7 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
break;
case LocationName.Sector12PowerhouseGym.toLowerCase():
if (player.city != CityName.Sector12) {
workerScript.log(
"gymWorkout",
_ctx.log(
() =>
`You cannot workout at '${LocationName.Sector12PowerhouseGym}' because you are not in '${CityName.Sector12}'.`,
);
@ -415,8 +398,7 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
break;
case LocationName.VolhavenMilleniumFitnessGym.toLowerCase():
if (player.city != CityName.Volhaven) {
workerScript.log(
"gymWorkout",
_ctx.log(
() =>
`You cannot workout at '${LocationName.VolhavenMilleniumFitnessGym}' because you are not in '${CityName.Volhaven}'.`,
);
@ -427,7 +409,7 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
expMult = 4;
break;
default:
workerScript.log("gymWorkout", () => `Invalid gym name: ${gymName}. gymWorkout() failed`);
_ctx.log(() => `Invalid gym name: ${gymName}. gymWorkout() failed`);
return false;
}
@ -449,7 +431,7 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
player.startClass(costMult, expMult, CONSTANTS.ClassGymAgility);
break;
default:
workerScript.log("gymWorkout", () => `Invalid stat: ${stat}.`);
_ctx.log(() => `Invalid stat: ${stat}.`);
return false;
}
if (focus) {
@ -459,7 +441,7 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
player.stopFocusing();
Router.toTerminal();
}
workerScript.log("gymWorkout", () => `Started training ${stat} at ${gymName}`);
_ctx.log(() => `Started training ${stat} at ${gymName}`);
return true;
},
@ -476,12 +458,12 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
case CityName.Ishima:
case CityName.Volhaven:
if (player.money < CONSTANTS.TravelCost) {
workerScript.log("travelToCity", () => "Not enough money to travel.");
_ctx.log(() => "Not enough money to travel.");
return false;
}
player.loseMoney(CONSTANTS.TravelCost, "other");
player.city = cityName;
workerScript.log("travelToCity", () => `Traveled to ${cityName}`);
_ctx.log(() => `Traveled to ${cityName}`);
player.gainIntelligenceExp(CONSTANTS.IntelligenceSingFnBaseExpGain / 50000);
return true;
default:
@ -494,12 +476,12 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
_ctx.helper.checkSingularityAccess();
if (player.hasTorRouter()) {
workerScript.log("purchaseTor", () => "You already have a TOR router!");
_ctx.log(() => "You already have a TOR router!");
return true;
}
if (player.money < CONSTANTS.TorRouterCost) {
workerScript.log("purchaseTor", () => "You cannot afford to purchase a Tor router.");
_ctx.log(() => "You cannot afford to purchase a Tor router.");
return false;
}
player.loseMoney(CONSTANTS.TorRouterCost, "other");
@ -518,7 +500,7 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
player.getHomeComputer().serversOnNetwork.push(darkweb.hostname);
darkweb.serversOnNetwork.push(player.getHomeComputer().hostname);
player.gainIntelligenceExp(CONSTANTS.IntelligenceSingFnBaseExpGain / 500);
workerScript.log("purchaseTor", () => "You have purchased a Tor router!");
_ctx.log(() => "You have purchased a Tor router!");
return true;
},
purchaseProgram: (_ctx: NetscriptContext) =>
@ -527,26 +509,25 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
const programName = _ctx.helper.string("programName", _programName).toLowerCase();
if (!player.hasTorRouter()) {
workerScript.log("purchaseProgram", () => "You do not have the TOR router.");
_ctx.log(() => "You do not have the TOR router.");
return false;
}
const item = Object.values(DarkWebItems).find((i) => i.program.toLowerCase() === programName);
if (item == null) {
workerScript.log("purchaseProgram", () => `Invalid program name: '${programName}.`);
_ctx.log(() => `Invalid program name: '${programName}.`);
return false;
}
if (player.money < item.price) {
workerScript.log(
"purchaseProgram",
_ctx.log(
() => `Not enough money to purchase '${item.program}'. Need ${numeralWrapper.formatMoney(item.price)}`,
);
return false;
}
if (player.hasProgram(item.program)) {
workerScript.log("purchaseProgram", () => `You already have the '${item.program}' program`);
_ctx.log(() => `You already have the '${item.program}' program`);
return true;
}
@ -558,8 +539,7 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
}
player.loseMoney(item.price, "other");
workerScript.log(
"purchaseProgram",
_ctx.log(
() => `You have purchased the '${item.program}' program. The new program can be found on your home computer.`,
);
player.gainIntelligenceExp(CONSTANTS.IntelligenceSingFnBaseExpGain / 5000);
@ -630,7 +610,7 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
_ctx.helper.checkSingularityAccess();
const baseserver = player.getCurrentServer();
if (!(baseserver instanceof Server)) {
workerScript.log("installBackdoor", () => "cannot backdoor this kind of server");
_ctx.log(() => "cannot backdoor this kind of server");
return Promise.resolve();
}
const server = baseserver as Server;
@ -642,13 +622,12 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
throw _ctx.helper.makeRuntimeErrorMsg(canHack.msg || "");
}
workerScript.log(
"installBackdoor",
_ctx.log(
() => `Installing backdoor on '${server.hostname}' in ${convertTimeMsToTimeElapsedString(installTime, true)}`,
);
return netscriptDelay(installTime, workerScript).then(function () {
workerScript.log("installBackdoor", () => `Successfully installed backdoor on '${server.hostname}'`);
_ctx.log(() => `Successfully installed backdoor on '${server.hostname}'`);
server.backdoorInstalled = true;
@ -695,7 +674,7 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
getStats: (_ctx: NetscriptContext) =>
function (): PlayerSkills {
_ctx.helper.checkSingularityAccess();
workerScript.log("getStats", () => `getStats is deprecated, please use getplayer`);
_ctx.log(() => `getStats is deprecated, please use getplayer`);
return {
hacking: player.hacking,
@ -710,10 +689,7 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
getCharacterInformation: (_ctx: NetscriptContext) =>
function (): CharacterInfo {
_ctx.helper.checkSingularityAccess();
workerScript.log(
"getCharacterInformation",
() => `getCharacterInformation is deprecated, please use getplayer`,
);
_ctx.log(() => `getCharacterInformation is deprecated, please use getplayer`);
return {
bitnode: player.bitNodeN,
@ -764,7 +740,7 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
function (): void {
_ctx.helper.checkSingularityAccess();
if (player.isWorking || Router.page() === Page.Infiltration || Router.page() === Page.BitVerse) {
workerScript.log("hospitalize", () => "Cannot go to the hospital because the player is busy.");
_ctx.log(() => "Cannot go to the hospital because the player is busy.");
return;
}
player.hospitalize();
@ -783,7 +759,7 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
Router.toTerminal();
}
const txt = player.singularityStopWork();
workerScript.log("stopAction", () => txt);
_ctx.log(() => txt);
return true;
}
return false;
@ -795,16 +771,13 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
// Check if we're at max cores
const homeComputer = player.getHomeComputer();
if (homeComputer.cpuCores >= 8) {
workerScript.log("upgradeHomeCores", () => `Your home computer is at max cores.`);
_ctx.log(() => `Your home computer is at max cores.`);
return false;
}
const cost = player.getUpgradeHomeCoresCost();
if (player.money < cost) {
workerScript.log(
"upgradeHomeCores",
() => `You don't have enough money. Need ${numeralWrapper.formatMoney(cost)}`,
);
_ctx.log(() => `You don't have enough money. Need ${numeralWrapper.formatMoney(cost)}`);
return false;
}
@ -812,10 +785,7 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
player.loseMoney(cost, "servers");
player.gainIntelligenceExp(CONSTANTS.IntelligenceSingFnBaseExpGain * 2);
workerScript.log(
"upgradeHomeCores",
() => `Purchased an additional core for home computer! It now has ${homeComputer.cpuCores} cores.`,
);
_ctx.log(() => `Purchased an additional core for home computer! It now has ${homeComputer.cpuCores} cores.`);
return true;
},
getUpgradeHomeCoresCost: (_ctx: NetscriptContext) =>
@ -831,16 +801,13 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
// Check if we're at max RAM
const homeComputer = player.getHomeComputer();
if (homeComputer.maxRam >= CONSTANTS.HomeComputerMaxRam) {
workerScript.log("upgradeHomeRam", () => `Your home computer is at max RAM.`);
_ctx.log(() => `Your home computer is at max RAM.`);
return false;
}
const cost = player.getUpgradeHomeRamCost();
if (player.money < cost) {
workerScript.log(
"upgradeHomeRam",
() => `You don't have enough money. Need ${numeralWrapper.formatMoney(cost)}`,
);
_ctx.log(() => `You don't have enough money. Need ${numeralWrapper.formatMoney(cost)}`);
return false;
}
@ -848,8 +815,7 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
player.loseMoney(cost, "servers");
player.gainIntelligenceExp(CONSTANTS.IntelligenceSingFnBaseExpGain * 2);
workerScript.log(
"upgradeHomeRam",
_ctx.log(
() =>
`Purchased additional RAM for home computer! It now has ${numeralWrapper.formatRAM(
homeComputer.maxRam,
@ -876,13 +842,13 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
// Make sure its a valid company
if (companyName == null || companyName === "" || !(Companies[companyName] instanceof Company)) {
workerScript.log("workForCompany", () => `Invalid company: '${companyName}'`);
_ctx.log(() => `Invalid company: '${companyName}'`);
return false;
}
// Make sure player is actually employed at the comapny
if (!Object.keys(player.jobs).includes(companyName)) {
workerScript.log("workForCompany", () => `You do not have a job at '${companyName}'`);
_ctx.log(() => `You do not have a job at '${companyName}'`);
return false;
}
@ -890,14 +856,14 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
const companyPositionName = player.jobs[companyName];
const companyPosition = CompanyPositions[companyPositionName];
if (companyPositionName === "" || !(companyPosition instanceof CompanyPosition)) {
workerScript.log("workForCompany", () => "You do not have a job");
_ctx.log(() => "You do not have a job");
return false;
}
const wasFocused = player.focus;
if (player.isWorking) {
const txt = player.singularityStopWork();
workerScript.log("workForCompany", () => txt);
_ctx.log(() => txt);
}
if (companyPosition.isPartTimeJob()) {
@ -913,10 +879,7 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
player.stopFocusing();
Router.toTerminal();
}
workerScript.log(
"workForCompany",
() => `Began working at '${player.companyName}' as a '${companyPositionName}'`,
);
_ctx.log(() => `Began working at '${player.companyName}' as a '${companyPositionName}'`);
return true;
},
applyToCompany: (_ctx: NetscriptContext) =>
@ -969,25 +932,19 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
res = player.applyForPartTimeWaiterJob(true);
break;
default:
workerScript.log("applyToCompany", () => `Invalid job: '${field}'.`);
_ctx.log(() => `Invalid job: '${field}'.`);
return false;
}
// TODO https://github.com/danielyxie/bitburner/issues/1378
// The player object's applyForJob function can return string with special error messages
// if (isString(res)) {
// workerScript.log("applyToCompany",()=> res);
// _ctx.log("applyToCompany",()=> res);
// return false;
// }
if (res) {
workerScript.log(
"applyToCompany",
() => `You were offered a new job at '${companyName}' as a '${player.jobs[companyName]}'`,
);
_ctx.log(() => `You were offered a new job at '${companyName}' as a '${player.jobs[companyName]}'`);
} else {
workerScript.log(
"applyToCompany",
() => `You failed to get a new job/promotion at '${companyName}' in the '${field}' field.`,
);
_ctx.log(() => `You failed to get a new job/promotion at '${companyName}' in the '${field}' field.`);
}
return res;
},
@ -1025,7 +982,7 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
getFaction(_ctx, facName);
if (!player.factionInvitations.includes(facName)) {
workerScript.log("joinFaction", () => `You have not been invited by faction '${facName}'`);
_ctx.log(() => `You have not been invited by faction '${facName}'`);
return false;
}
const fac = Factions[facName];
@ -1039,7 +996,7 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
}
}
player.gainIntelligenceExp(CONSTANTS.IntelligenceSingFnBaseExpGain * 5);
workerScript.log("joinFaction", () => `Joined the '${facName}' faction.`);
_ctx.log(() => `Joined the '${facName}' faction.`);
return true;
},
workForFaction: (_ctx: NetscriptContext) =>
@ -1052,22 +1009,19 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
// if the player is in a gang and the target faction is any of the gang faction, fail
if (player.inGang() && faction.name === player.getGangFaction().name) {
workerScript.log(
"workForFaction",
() => `You can't work for '${facName}' because youre managing a gang for it`,
);
_ctx.log(() => `You can't work for '${facName}' because youre managing a gang for it`);
return false;
}
if (!player.factions.includes(facName)) {
workerScript.log("workForFaction", () => `You are not a member of '${facName}'`);
_ctx.log(() => `You are not a member of '${facName}'`);
return false;
}
const wasFocusing = player.focus;
if (player.isWorking) {
const txt = player.singularityStopWork();
workerScript.log("workForFaction", () => txt);
_ctx.log(() => txt);
}
switch (type.toLowerCase()) {
@ -1075,10 +1029,7 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
case "hacking contracts":
case "hackingcontracts":
if (!FactionInfos[faction.name].offerHackingWork) {
workerScript.log(
"workForFaction",
() => `Faction '${faction.name}' do not need help with hacking contracts.`,
);
_ctx.log(() => `Faction '${faction.name}' do not need help with hacking contracts.`);
return false;
}
player.startFactionHackWork(faction);
@ -1089,16 +1040,13 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
player.stopFocusing();
Router.toTerminal();
}
workerScript.log("workForFaction", () => `Started carrying out hacking contracts for '${faction.name}'`);
_ctx.log(() => `Started carrying out hacking contracts for '${faction.name}'`);
return true;
case "field":
case "fieldwork":
case "field work":
if (!FactionInfos[faction.name].offerFieldWork) {
workerScript.log(
"workForFaction",
() => `Faction '${faction.name}' do not need help with field missions.`,
);
_ctx.log(() => `Faction '${faction.name}' do not need help with field missions.`);
return false;
}
player.startFactionFieldWork(faction);
@ -1109,16 +1057,13 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
player.stopFocusing();
Router.toTerminal();
}
workerScript.log("workForFaction", () => `Started carrying out field missions for '${faction.name}'`);
_ctx.log(() => `Started carrying out field missions for '${faction.name}'`);
return true;
case "security":
case "securitywork":
case "security work":
if (!FactionInfos[faction.name].offerSecurityWork) {
workerScript.log(
"workForFaction",
() => `Faction '${faction.name}' do not need help with security work.`,
);
_ctx.log(() => `Faction '${faction.name}' do not need help with security work.`);
return false;
}
player.startFactionSecurityWork(faction);
@ -1129,10 +1074,10 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
player.stopFocusing();
Router.toTerminal();
}
workerScript.log("workForFaction", () => `Started carrying out security work for '${faction.name}'`);
_ctx.log(() => `Started carrying out security work for '${faction.name}'`);
return true;
default:
workerScript.log("workForFaction", () => `Invalid work type: '${type}`);
_ctx.log(() => `Invalid work type: '${type}`);
return false;
}
},
@ -1164,38 +1109,28 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
const amt = _ctx.helper.number("amt", _amt);
const faction = getFaction(_ctx, facName);
if (!player.factions.includes(faction.name)) {
workerScript.log("donateToFaction", () => `You can't donate to '${facName}' because you aren't a member`);
_ctx.log(() => `You can't donate to '${facName}' because you aren't a member`);
return false;
}
if (player.inGang() && faction.name === player.getGangFaction().name) {
workerScript.log(
"donateToFaction",
() => `You can't donate to '${facName}' because youre managing a gang for it`,
);
_ctx.log(() => `You can't donate to '${facName}' because youre managing a gang for it`);
return false;
}
if (faction.name === FactionNames.ChurchOfTheMachineGod || faction.name === FactionNames.Bladeburners) {
workerScript.log(
"donateToFaction",
() => `You can't donate to '${facName}' because they do not accept donations`,
);
_ctx.log(() => `You can't donate to '${facName}' because they do not accept donations`);
return false;
}
if (typeof amt !== "number" || amt <= 0 || isNaN(amt)) {
workerScript.log("donateToFaction", () => `Invalid donation amount: '${amt}'.`);
_ctx.log(() => `Invalid donation amount: '${amt}'.`);
return false;
}
if (player.money < amt) {
workerScript.log(
"donateToFaction",
() => `You do not have enough money to donate ${numeralWrapper.formatMoney(amt)} to '${facName}'`,
);
_ctx.log(() => `You do not have enough money to donate ${numeralWrapper.formatMoney(amt)} to '${facName}'`);
return false;
}
const repNeededToDonate = Math.floor(CONSTANTS.BaseFavorToDonate * BitNodeMultipliers.RepToDonateToFaction);
if (faction.favor < repNeededToDonate) {
workerScript.log(
"donateToFaction",
_ctx.log(
() =>
`You do not have enough favor to donate to this faction. Have ${faction.favor}, need ${repNeededToDonate}`,
);
@ -1204,8 +1139,7 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
const repGain = (amt / CONSTANTS.DonateMoneyToRepDivisor) * player.faction_rep_mult;
faction.playerReputation += repGain;
player.loseMoney(amt, "other");
workerScript.log(
"donateToFaction",
_ctx.log(
() =>
`${numeralWrapper.formatMoney(amt)} donated to '${facName}' for ${numeralWrapper.formatReputation(
repGain,
@ -1222,32 +1156,29 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
const wasFocusing = player.focus;
if (player.isWorking) {
const txt = player.singularityStopWork();
workerScript.log("createProgram", () => txt);
_ctx.log(() => txt);
}
const p = Object.values(Programs).find((p) => p.name.toLowerCase() === programName);
if (p == null) {
workerScript.log("createProgram", () => `The specified program does not exist: '${programName}`);
_ctx.log(() => `The specified program does not exist: '${programName}`);
return false;
}
if (player.hasProgram(p.name)) {
workerScript.log("createProgram", () => `You already have the '${p.name}' program`);
_ctx.log(() => `You already have the '${p.name}' program`);
return false;
}
const create = p.create;
if (create === null) {
workerScript.log("createProgram", () => `You cannot create the '${p.name}' program`);
_ctx.log(() => `You cannot create the '${p.name}' program`);
return false;
}
if (!create.req(player)) {
workerScript.log(
"createProgram",
() => `Hacking level is too low to create '${p.name}' (level ${create.level} req)`,
);
_ctx.log(() => `Hacking level is too low to create '${p.name}' (level ${create.level} req)`);
return false;
}
@ -1259,7 +1190,7 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
player.stopFocusing();
Router.toTerminal();
}
workerScript.log("createProgram", () => `Began creating program: '${programName}'`);
_ctx.log(() => `Began creating program: '${programName}'`);
return true;
},
commitCrime: (_ctx: NetscriptContext) =>
@ -1269,7 +1200,7 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
if (player.isWorking) {
const txt = player.singularityStopWork();
workerScript.log("commitCrime", () => txt);
_ctx.log(() => txt);
}
// Set Location to slums
@ -1280,7 +1211,7 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
// couldn't find crime
throw _ctx.helper.makeRuntimeErrorMsg(`Invalid crime: '${crimeRoughName}'`);
}
workerScript.log("commitCrime", () => `Attempting to commit ${crime.name}...`);
_ctx.log(() => `Attempting to commit ${crime.name}...`);
return crime.commit(Router, player, 1, workerScript);
},
getCrimeChance: (_ctx: NetscriptContext) =>
@ -1313,7 +1244,7 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
// If we don't have Tor, log it and return [] (empty list)
if (!player.hasTorRouter()) {
workerScript.log("getDarkwebPrograms", () => "You do not have the TOR router.");
_ctx.log(() => "You do not have the TOR router.");
return [];
}
return Object.values(DarkWebItems).map((p) => p.program);
@ -1325,7 +1256,7 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
// If we don't have Tor, log it and return -1
if (!player.hasTorRouter()) {
workerScript.log("getDarkwebProgramCost", () => "You do not have the TOR router.");
_ctx.log(() => "You do not have the TOR router.");
// returning -1 rather than throwing an error to be consistent with purchaseProgram
// which returns false if tor has
return -1;
@ -1345,7 +1276,7 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
}
if (player.hasProgram(item.program)) {
workerScript.log("getDarkwebProgramCost", () => `You already have the '${item.program}' program`);
_ctx.log(() => `You already have the '${item.program}' program`);
return 0;
}
return item.price;

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

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

@ -73,7 +73,6 @@ export interface IPlayer {
terminalCommandHistory: string[];
lastUpdate: number;
totalPlaytime: number;
hasCompletedAnInfiltration: boolean;
// Stats
hacking: number;
@ -124,12 +123,6 @@ export interface IPlayer {
bladeburner_stamina_gain_mult: number;
bladeburner_analysis_mult: number;
bladeburner_success_chance_mult: number;
infiltration_base_rep_increase: number;
infiltration_rep_mult: number;
infiltration_trade_mult: number;
infiltration_sell_mult: number;
infiltration_timer_mult: number;
infiltration_damage_reduction_mult: number;
createProgramReqLvl: number;
factionWorkType: string;

@ -83,7 +83,6 @@ export class PlayerObject implements IPlayer {
lastUpdate: number;
lastSave: number;
totalPlaytime: number;
hasCompletedAnInfiltration: boolean;
// Stats
hacking: number;
@ -134,12 +133,6 @@ export class PlayerObject implements IPlayer {
bladeburner_stamina_gain_mult: number;
bladeburner_analysis_mult: number;
bladeburner_success_chance_mult: number;
infiltration_base_rep_increase: number;
infiltration_rep_mult: number;
infiltration_trade_mult: number;
infiltration_sell_mult: number;
infiltration_timer_mult: number;
infiltration_damage_reduction_mult: number;
createProgramReqLvl: number;
factionWorkType: string;
@ -472,13 +465,6 @@ export class PlayerObject implements IPlayer {
this.bladeburner_analysis_mult = 1; //Field Analysis Onl;
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;
// Sleeves & Re-sleeving
this.sleeves = [];
this.sleevesFromCovenant = 0; // # of Duplicate sleeves purchased from the covenan;
@ -491,7 +477,6 @@ export class PlayerObject implements IPlayer {
this.lastUpdate = 0;
this.lastSave = 0;
this.totalPlaytime = 0;
this.hasCompletedAnInfiltration = false;
this.playtimeSinceLastAug = 0;
this.playtimeSinceLastBitnode = 0;

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

@ -159,7 +159,6 @@ export function prestigeAugmentation(this: PlayerObject): void {
this.workChaExpGained = 0;
this.workRepGained = 0;
this.workMoneyGained = 0;
this.hasCompletedAnInfiltration = false;
this.timeWorked = 0;
@ -312,13 +311,6 @@ export function resetMultipliers(this: IPlayer): void {
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;
}
export function hasProgram(this: IPlayer, programName: string): boolean {
@ -1877,13 +1869,12 @@ export function getNextCompanyPosition(
export function quitJob(this: IPlayer, company: string): void {
if (this.isWorking == true && this.workType.includes("Working for Company") && this.companyName == company) {
this.isWorking = false;
this.companyName = "";
}
if (this.companyName === company) {
this.companyName = "";
this.finishWork(true);
}
delete this.jobs[company];
if (this.companyName === company) {
this.companyName = this.hasJob() ? Object.keys(this.jobs)[0] : "";
}
}
/**
@ -2151,12 +2142,6 @@ export function checkForFactionInvitations(this: IPlayer): Faction[] {
return allCompanies.includes(companyName) && getCompanyRep(companyName) > repNeeded;
}
//Infiltrators
const InfiltratorsFac = Factions[FactionNames.Infiltrators];
if (this.hasCompletedAnInfiltration && !InfiltratorsFac.isMember && !InfiltratorsFac.alreadyInvited) {
invitedFactions.push(InfiltratorsFac);
}
//Illuminati
const illuminatiFac = Factions[FactionNames.Illuminati];
if (
@ -2180,7 +2165,7 @@ export function checkForFactionInvitations(this: IPlayer): Faction[] {
!daedalusFac.isBanned &&
!daedalusFac.isMember &&
!daedalusFac.alreadyInvited &&
numAugmentations >= Math.round(30 * BitNodeMultipliers.DaedalusAugsRequirement) &&
numAugmentations >= BitNodeMultipliers.DaedalusAugsRequirement &&
this.money >= 100000000000 &&
(this.hacking >= 2500 ||
(this.strength >= 1500 && this.defense >= 1500 && this.dexterity >= 1500 && this.agility >= 1500))

@ -56,7 +56,7 @@ function possibleJobs(player: IPlayer, sleeve: Sleeve): string[] {
function possibleFactions(player: IPlayer, sleeve: Sleeve): string[] {
// Array of all factions that other sleeves are working for
const forbiddenFactions = [FactionNames.Bladeburners as string, FactionNames.Infiltrators as string];
const forbiddenFactions = [FactionNames.Bladeburners as string, FactionNames.ShadowsOfAnarchy as string];
if (player.gang) {
forbiddenFactions.push(player.gang.facName);
}

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

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

@ -4261,12 +4261,22 @@ interface Stanek {
* @returns The fragment at [rootX, rootY], if any.
*/
removeFragment(rootX: number, rootY: number): boolean;
/**
* Accept Stanek's Gift by joining the Church of the Machine God
* @remarks
* RAM cost: 2 GB
*
* @returns true if the player is a member of the church and has the gift installed,
* false otherwise.
*/
acceptGift(): boolean;
}
export interface InfiltrationReward {
tradeRep: number;
sellCash: number;
infiltratorRep: number;
SoARep: number;
}
export interface InfiltrationLocation {
@ -4280,24 +4290,6 @@ export interface InfiltrationLocation {
* @public
*/
interface Infiltration {
/**
* Calculate the difficulty of performing an infiltration.
* @remarks
* RAM cost: 2.5 GB
*
* @param locationName - name of the location to check.
* @returns the difficulty.
*/
calculateDifficulty(locationName: string): number;
/**
* Calculate the rewards for trading and selling information for completing an infiltration.
* @remarks
* RAM cost: 2.5 GB
*
* @param locationName - name of the location to check.
* @returns the trade reputation, sell cash and infiltrators rep reward.
*/
calculateRewards(locationName: string): InfiltrationReward;
/**
* Get all locations that can be infiltrated.
* @remarks
@ -4305,15 +4297,15 @@ interface Infiltration {
*
* @returns all locations that can be infiltrated.
*/
getLocations(): any[];
getPossibleLocations(): string[];
/**
* Get all infiltrations with difficulty, location and rewards.
* @remarks
* RAM cost: 15 GB
*
* @returns all infiltrations with difficulty, location and rewards.
* @returns Infiltration data for given location.
*/
getInfiltrations(): InfiltrationLocation[];
getInfiltration(location: string): InfiltrationLocation;
}
/**

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

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

@ -3,8 +3,9 @@ import Typography from "@mui/material/Typography";
import { Theme } from "@mui/material/styles";
import makeStyles from "@mui/styles/makeStyles";
import createStyles from "@mui/styles/createStyles";
import Paper from "@mui/material/Paper";
import Popper from "@mui/material/Popper";
import TextField from "@mui/material/TextField";
import Tooltip from "@mui/material/Tooltip";
import { KEY } from "../../utils/helpers/keyCodes";
import { ITerminal } from "../ITerminal";
@ -376,46 +377,40 @@ export function TerminalInput({ terminal, router, player }: IProps): React.React
return (
<>
<Tooltip
title={
possibilities.length > 0 ? (
<>
<Typography classes={{ root: classes.preformatted }} color={"primary"} paragraph={false}>
Possible autocomplete candidate:
</Typography>
<Typography classes={{ root: classes.preformatted }} color={"primary"} paragraph={false}>
{possibilities.join(" ")}
</Typography>
</>
) : (
""
)
}
>
<TextField
fullWidth
color={terminal.action === null ? "primary" : "secondary"}
autoFocus
disabled={terminal.action !== null}
autoComplete="off"
value={value}
classes={{ root: classes.textfield }}
onChange={handleValueChange}
inputRef={terminalInput}
InputProps={{
// for players to hook in
id: "terminal-input",
className: classes.input,
startAdornment: (
<Typography color={terminal.action === null ? "primary" : "secondary"} flexShrink={0}>
[{player.getCurrentServer().hostname}&nbsp;~{terminal.cwd()}]&gt;&nbsp;
</Typography>
),
spellCheck: false,
onKeyDown: onKeyDown,
}}
></TextField>
</Tooltip>
<TextField
fullWidth
color={terminal.action === null ? "primary" : "secondary"}
autoFocus
disabled={terminal.action !== null}
autoComplete="off"
value={value}
classes={{ root: classes.textfield }}
onChange={handleValueChange}
inputRef={terminalInput}
InputProps={{
// for players to hook in
id: "terminal-input",
className: classes.input,
startAdornment: (
<Typography color={terminal.action === null ? "primary" : "secondary"} flexShrink={0}>
[{player.getCurrentServer().hostname}&nbsp;~{terminal.cwd()}]&gt;&nbsp;
</Typography>
),
spellCheck: false,
onBlur: () => setPossibilities([]),
onKeyDown: onKeyDown,
}}
></TextField>
<Popper open={possibilities.length > 0} anchorEl={terminalInput.current} placement={"top-start"}>
<Paper sx={{ m: 1, p: 2 }}>
<Typography classes={{ root: classes.preformatted }} color={"primary"} paragraph={false}>
Possible autocomplete candidates:
</Typography>
<Typography classes={{ root: classes.preformatted }} color={"primary"} paragraph={false}>
{possibilities.join(" ")}
</Typography>
</Paper>
</Popper>
</>
);
}

@ -1306,4 +1306,154 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [
return parseInt(ans, 10) === HammingDecode(data);
},
},
{
name: "Proper 2-Coloring of a Graph",
difficulty: 7,
numTries: 5,
desc: (data: [number, [number, number][]]): string => {
return [
`You are given the following data, representing a graph:\n`,
`${JSON.stringify(data)}\n`,
`Note that "graph", as used here, refers to the field of graph theory, and has`,
`no relation to statistics or plotting.`,
`The first element of the data represents the number of vertices in the graph.`,
`Each vertex is a unique number between 0 and ${data[0] - 1}.`,
`The next element of the data represents the edges of the graph.`,
`Two vertices u,v in a graph are said to be adjacent if there exists an edge [u,v].`,
`Note that an edge [u,v] is the same as an edge [v,u], as order does not matter.`,
`You must construct a 2-coloring of the graph, meaning that you have to assign each`,
`vertex in the graph a "color", either 0 or 1, such that no two adjacent vertices have`,
`the same color. Submit your answer in the form of an array, where element i`,
`represents the color of vertex i. If it is impossible to construct a 2-coloring of`,
`the given graph, instead submit an empty array.\n\n`,
`Examples:\n\n`,
`Input: [4, [[0, 2], [0, 3], [1, 2], [1, 3]]]\n`,
`Output: [0, 0, 1, 1]\n\n`,
`Input: [3, [[0, 1], [0, 2], [1, 2]]]\n`,
`Output: []`,
].join(" ");
},
gen: (): [number, [number, number][]] => {
//Generate two partite sets
const n = Math.floor(Math.random() * 5) + 3;
const m = Math.floor(Math.random() * 5) + 3;
//50% chance of spawning any given valid edge in the bipartite graph
const edges: [number, number][] = [];
for (let i = 0; i < n; i++) {
for (let j = 0; j < m; j++) {
if (Math.random() > 0.5) {
edges.push([i, n + j]);
}
}
}
//Add an edge at random with no regard to partite sets
let a = Math.floor(Math.random() * (n + m));
let b = Math.floor(Math.random() * (n + m));
if (a > b) [a, b] = [b, a]; //Enforce lower numbers come first
if (a != b && !edges.includes([a, b])) {
edges.push([a, b]);
}
//Randomize array in-place using Durstenfeld shuffle algorithm.
function shuffle(array: any[]): void {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]];
}
}
//Replace instances of the original vertex names in-place
const vertexShuffler = Array.from(Array(n + m).keys());
shuffle(vertexShuffler);
for (let i = 0; i < edges.length; i++) {
edges[i] = [vertexShuffler[edges[i][0]], vertexShuffler[edges[i][1]]];
if (edges[i][0] > edges[i][1]) {
//Enforce lower numbers come first
[edges[i][0], edges[i][1]] = [edges[i][1], edges[i][0]];
}
}
//Shuffle the order of the edges themselves, as well
shuffle(edges);
return [n + m, edges];
},
solver: (data: [number, [number, number][]], ans: string): boolean => {
//Case where the player believes there is no solution
if (ans == "[]") {
//Helper function to get neighbourhood of a vertex
function neighbourhood(vertex: number): number[] {
const adjLeft = data[1].filter(([a, _]) => a == vertex).map(([_, b]) => b);
const adjRight = data[1].filter(([_, b]) => b == vertex).map(([a, _]) => a);
return adjLeft.concat(adjRight);
}
//Verify that there is no solution by attempting to create a proper 2-coloring.
const coloring: (number | undefined)[] = Array(data[0]).fill(undefined);
while (coloring.some((val) => val === undefined)) {
//Color a vertex in the graph
const initialVertex: number = coloring.findIndex((val) => val === undefined);
coloring[initialVertex] = 0;
const frontier: number[] = [initialVertex];
//Propogate the coloring throughout the component containing v greedily
while (frontier.length > 0) {
const v: number = frontier.pop() || 0;
const neighbors: number[] = neighbourhood(v);
//For each vertex u adjacent to v
for (const id in neighbors) {
const u: number = neighbors[id];
//Set the color of u to the opposite of v's color if it is new,
//then add u to the frontier to continue the algorithm.
if (coloring[u] === undefined) {
if (coloring[v] === 0) coloring[u] = 1;
else coloring[u] = 0;
frontier.push(u);
}
//Assert u,v do not have the same color
else if (coloring[u] === coloring[v]) {
//If u,v do have the same color, no proper 2-coloring exists, meaning
//the player was correct to say there is no proper 2-coloring of the graph.
return true;
}
}
}
}
//If this code is reached, there exists a proper 2-coloring of the input
//graph, and thus the player was incorrect in submitting no answer.
return false;
}
//Sanitize player input
const sanitizedPlayerAns: string = removeBracketsFromArrayString(ans);
const sanitizedPlayerAnsArr: string[] = sanitizedPlayerAns.split(",");
const coloring: number[] = sanitizedPlayerAnsArr.map((val) => parseInt(val));
//Solution provided case
if (coloring.length == data[0]) {
const edges = data[1];
const validColors = [0, 1];
//Check that the provided solution is a proper 2-coloring
return edges.every(([a, b]) => {
const aColor = coloring[a];
const bColor = coloring[b];
return (
validColors.includes(aColor) && //Enforce the first endpoint is color 0 or 1
validColors.includes(bColor) && //Enforce the second endpoint is color 0 or 1
aColor != bColor //Enforce the endpoints are different colors
);
});
}
//Return false if the coloring is the wrong size
else return false;
},
},
];

@ -14,7 +14,6 @@ import { Modal } from "./React/Modal";
import { Money } from "./React/Money";
import { StatsRow } from "./React/StatsRow";
import { StatsTable } from "./React/StatsTable";
import { FactionNames } from "../Faction/data/FactionNames";
interface EmployersModalProps {
open: boolean;
@ -83,13 +82,12 @@ function CurrentBitNode(): React.ReactElement {
const player = use.Player();
if (player.sourceFiles.length > 0) {
const index = "BitNode" + player.bitNodeN;
const currentSourceFile = player.sourceFiles.find((sourceFile) => sourceFile.n == player.bitNodeN);
const lvl = currentSourceFile ? currentSourceFile.lvl : 0;
const lvl = player.sourceFileLvl(player.bitNodeN) + 1;
return (
<Box>
<Paper sx={{ p: 1 }}>
<Typography variant="h5">
BitNode {player.bitNodeN}: {BitNodes[index].name} (Level {lvl + 1})
BitNode {player.bitNodeN}: {BitNodes[index].name} (Level {lvl})
</Typography>
<Typography sx={{ whiteSpace: "pre-wrap", overflowWrap: "break-word" }}>{BitNodes[index].info}</Typography>
</Paper>
@ -496,18 +494,6 @@ export function CharacterStats(): React.ReactElement {
noMargin
/>
)}
{player.factions.includes(FactionNames.Infiltrators) && (
<MultiplierTable
color={Settings.theme.primary}
rows={[
["Infiltrator Rep reward", player.infiltration_rep_mult],
["Infiltration sell", player.infiltration_sell_mult],
["Infiltration trade", player.infiltration_trade_mult],
["Infiltration minigame timer", player.infiltration_timer_mult],
["Infiltration minigame damage reduction", -1 * (1 - player.infiltration_damage_reduction_mult)],
]}
/>
)}
</Box>
</Box>
</Paper>

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

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

@ -265,10 +265,22 @@ function LogWindow(props: IProps): React.ReactElement {
</Typography>
<Box position="absolute" right={0}>
{!workerScripts.has(script.pid) && <Button onClick={run}>Run</Button>}
{workerScripts.has(script.pid) && <Button onClick={kill}>Kill</Button>}
<Button onClick={minimize}>{minimized ? "\u{1F5D6}" : "\u{1F5D5}"}</Button>
<Button onClick={props.onClose}>Close</Button>
{!workerScripts.has(script.pid) && (
<Button onClick={run} onTouchEnd={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 onClick={props.onClose} onTouchEnd={props.onClose}>
Close
</Button>
</Box>
</Box>
</Paper>

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

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

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

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