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 FIX #xyzw is the issue number, if any
PLAYER DESCRIPTION is what you'd tell a non-contributor to convey what is changed. 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 # Documentation
- DO NOT CHANGE any markdown/\*.md, these files are autogenerated from NetscriptDefinitions.d.ts and will be overwritten - 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: In this BitNode:
* Your stats are significantly decreased * Your stats are significantly decreased
* You cannnot purchase additional servers * You cannot purchase additional servers
* Hacking is significantly less profitable * Hacking is significantly less profitable
Source-File Source-File

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

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

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

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

@ -5,7 +5,6 @@ import { Programs } from "../../Programs/Programs";
import { WHRNG } from "../../Casino/RNG"; import { WHRNG } from "../../Casino/RNG";
import React from "react"; import React from "react";
import { FactionNames } from "../../Faction/data/FactionNames"; import { FactionNames } from "../../Faction/data/FactionNames";
import { CityName } from "../../Locations/data/CityNames";
import { CONSTANTS } from "../../Constants"; import { CONSTANTS } from "../../Constants";
function getRandomBonus(): any { function getRandomBonus(): any {
@ -96,159 +95,96 @@ function getRandomBonus(): any {
return bonuses[Math.floor(bonuses.length * randomNumber.random())]; return bonuses[Math.floor(bonuses.length * randomNumber.random())];
} }
export const initInfiltratorsAugmentations = (): Augmentation[] => [ export const initSoAAugmentations = (): Augmentation[] => [
new Augmentation({ new Augmentation({
name: AugmentationNames.BionicFingers, name: AugmentationNames.WKSharmonizer,
repCost: 15e1, repCost: 1e4,
moneyCost: 1e6, moneyCost: 1e6,
infiltration_base_rep_increase: 5,
info: info:
"This state of the art augmentation removes the need for bones and tendons in your fingers, " + `A copy of the WKS harmonizer from the MIA leader of the ${FactionNames.ShadowsOfAnarchy} ` +
"with this you will have the dexterity equal to the best rubik's cube player in the world. ", "injects *Γ-based cells that provides general enhancement to the body.",
factions: [FactionNames.Infiltrators], 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({ new Augmentation({
name: AugmentationNames.CorporationManagementImplant, name: AugmentationNames.MightOfAres,
repCost: 25e1, repCost: 1e4,
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,
moneyCost: 1e6, moneyCost: 1e6,
info: info:
"You found an old jar apparently containing the brain stem of one of the most famous " + "Extra-occular neurons taken from old martial art master. Injecting the user the ability to " +
"fortune tellers in the world, installing this augmentation will apparently connect the synapses reactivating " + "predict enemy attack before they even know it themself.",
"the magic this man once shared with the world ",
stats: ( stats: (
<>This augmentation makes the Slash minigame easier by showing you via an indictor when the slash in coming.</> <>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({ new Augmentation({
name: AugmentationNames.IntellisenseModule, name: AugmentationNames.WisdomOfAthena,
repCost: 1e2, repCost: 1e4,
moneyCost: 1e6, moneyCost: 1e6,
info: info: "A connective brain implant to SASHA that focuses in pattern recognition and predictive templating.",
"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.",
stats: <>This augmentation makes the Bracket minigame easier by removing all '[' ']'.</>, stats: <>This augmentation makes the Bracket minigame easier by removing all '[' ']'.</>,
factions: [FactionNames.Infiltrators], factions: [FactionNames.ShadowsOfAnarchy],
}), }),
new Augmentation({ new Augmentation({
name: AugmentationNames.RearViewMirrorShoulderAttachment, name: AugmentationNames.ChaosOfDionysus,
repCost: 1e2, repCost: 1e4,
moneyCost: 1e6, moneyCost: 1e6,
info: "Never again will you need to turn your head to see whats behind you.", info: "Opto-occipito implant to process visual signal before brain interpretation.",
stats: <>This augmentation makes the Backwards minigame easier by making the words no longer backwards.</>, stats: <>This augmentation makes the Backwards minigame easier by flipping the words.</>,
factions: [FactionNames.Infiltrators], factions: [FactionNames.ShadowsOfAnarchy],
}), }),
new Augmentation({ new Augmentation({
name: AugmentationNames.KyberCrystalInjection, name: AugmentationNames.BeautyOfAphrodite,
repCost: 1e2, repCost: 1e4,
moneyCost: 1e6, moneyCost: 1e6,
info: info:
"A weird looking shiny crystal which is crushed down and turned into a thick syrup, " + "Pheromone extruder injected in the thoracodorsal nerve. Emits pleasing scent guaranteed to " +
"most people think its all homeopathic but there are a few that believe when injected people will " + "make conversational partners more agreeable.",
"believe what you say with one wave of the hand ",
stats: <>This augmentation makes the Bribe minigame easier by indicating the incorrect paths.</>, stats: <>This augmentation makes the Bribe minigame easier by indicating the incorrect paths.</>,
factions: [FactionNames.Infiltrators], factions: [FactionNames.ShadowsOfAnarchy],
}), }),
new Augmentation({ new Augmentation({
name: AugmentationNames.DyslexiaModule, name: AugmentationNames.TrickeryOfHermes,
repCost: 1e2, repCost: 1e4,
moneyCost: 1e6, moneyCost: 1e6,
info: info: "Penta-dynamo-neurovascular-valve inserted in the carpal ligament, enhances dexterity.",
"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",
stats: <>This augmentation makes the Cheat Code minigame easier by allowing the opposite character.</>, stats: <>This augmentation makes the Cheat Code minigame easier by allowing the opposite character.</>,
factions: [FactionNames.Infiltrators], factions: [FactionNames.ShadowsOfAnarchy],
}), }),
new Augmentation({ new Augmentation({
name: AugmentationNames.CyberDecoder, name: AugmentationNames.FloodOfPoseidon,
repCost: 1e2, repCost: 1e4,
moneyCost: 1e6, moneyCost: 1e6,
info: info: "Transtinatium VVD reticulator used in optico-sterbing recognition.",
"A cool looking do hickey that oddly resembles Keanu Reeves face, " +
"it has a usb cable that looks like it plugs into something.",
stats: <>This augmentation makes the Symbol matching minigame easier by indicating the correct choice.</>, stats: <>This augmentation makes the Symbol matching minigame easier by indicating the correct choice.</>,
factions: [FactionNames.Infiltrators], factions: [FactionNames.ShadowsOfAnarchy],
}), }),
new Augmentation({ new Augmentation({
name: AugmentationNames.MineDetectionArmAttachment, name: AugmentationNames.HuntOfArtemis,
repCost: 1e2, repCost: 1e4,
moneyCost: 1e6, moneyCost: 1e6,
info: info: "magneto-turboencabulator based on technology by Micha Eike Siemon, increases the users electro-magnetic sensitivity.",
"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.",
stats: ( stats: (
<> <>
This augmentation makes the Minesweeper minigame easier by showing the location of all mines and keeping their This augmentation makes the Minesweeper minigame easier by showing the location of all mines and keeping their
position. position.
</> </>
), ),
factions: [FactionNames.Infiltrators], factions: [FactionNames.ShadowsOfAnarchy],
}), }),
new Augmentation({ new Augmentation({
name: AugmentationNames.SecurityWireContacts, name: AugmentationNames.KnowledgeOfApollo,
repCost: 1e2, repCost: 1e4,
moneyCost: 1e6, moneyCost: 1e6,
info: info: "Neodynic retention fjengeln spoofer using -φ karmions, net positive effect on implantees delta wave.",
"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",
stats: <>This augmentation makes the Wire Cutting minigame easier by indicating the incorrect wires.</>, 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: ( stats: (
<> <>
This special augmentation can be leveled up infinitely. Each level of this augmentation increases MOST 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, hacking_chance_mult: 1.01 + donationBonus,
@ -2094,7 +2031,7 @@ export function initNeuroFluxGovernor(): Augmentation {
work_money_mult: 1.01 + donationBonus, work_money_mult: 1.01 + donationBonus,
factions: Object.values(FactionNames).filter( factions: Object.values(FactionNames).filter(
(factionName) => (factionName) =>
![FactionNames.Infiltrators, FactionNames.Bladeburners, FactionNames.ChurchOfTheMachineGod].includes( ![FactionNames.ShadowsOfAnarchy, FactionNames.Bladeburners, FactionNames.ChurchOfTheMachineGod].includes(
factionName, factionName,
), ),
), ),

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

@ -174,7 +174,7 @@ export function AugmentationsRoot(props: IProps): React.ReactElement {
</span> </span>
</Tooltip> </Tooltip>
<Tooltip title={<Typography>It's always a good idea to backup/export your save!</Typography>}> <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()} Backup Save {exportBonusStr()}
</Button> </Button>
</Tooltip> </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), const hasLeftImprovements = +!!(leftColData.filter((item) => item[2] !== 0).length > 0),
hasRightImprovements = +!!(rightColData.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 React from "react";
import { BitNodeMultipliers } from "./BitNodeMultipliers"; import { BitNodeMultipliers, IBitNodeMultipliers } from "./BitNodeMultipliers";
import { IPlayer } from "../PersonObjects/IPlayer"; import { IPlayer } from "../PersonObjects/IPlayer";
import { IMap } from "../types"; import { IMap } from "../types";
import { FactionNames } from "../Faction/data/FactionNames"; 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. savagery. The organized crime factions quickly rose to the top of the modern world.
<br /> <br />
<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},{" "} Certain Factions ({FactionNames.SlumSnakes}, {FactionNames.Tetrads}, {FactionNames.TheSyndicate},{" "}
{FactionNames.TheDarkArmy}, {FactionNames.SpeakersForTheDead}, {FactionNames.NiteSec}, {FactionNames.TheBlackHand} {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 ) 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 /> <br />
Every Augmentation in the game will be available through the Factions listed above Every Augmentation in the game will be available through the Factions listed above
<br /> <br />
For every Faction NOT listed above, reputation gains are halved
<br />
You will no longer gain passive reputation with Factions
<br />
<br /> <br />
Destroying this BitNode will give you Source-File 2, or if you already have this Source-File it will upgrade its 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 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 />
<br /> <br />
In this BitNode you can create and manage your own corporation. Running a successful corporation has the potential 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 /> of generating massive profits.
<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
<br /> <br />
<br /> <br />
Destroying this BitNode will give you Source-File 3, or if you already have this Source-File it will upgrade its 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 The Singularity has arrived. The human race is gone, replaced by artificially superintelligent beings that are
more machine than man. <br /> more machine than man. <br />
<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 /> <br />
In this BitNode you will gain access to a new set of Netscript Functions known as Singularity Functions. These 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, 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. couldn't be modeled by 1's and 0's. They were wrong.
<br /> <br />
<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 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 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 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 />
<br /> <br />
In this BitNode you will be able to access the {FactionNames.Bladeburners} Division at the NSA, which provides a In this BitNode you will be able to access the {FactionNames.Bladeburners} Division at the NSA, which provides a
new mechanic for progression. Furthermore: new mechanic for progression.
<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
<br /> <br />
<br /> <br />
Destroying this BitNode will give you Source-File 6, or if you already have this Source-File it will upgrade its 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 /> <br />
In this BitNode you will be able to access the {FactionNames.Bladeburners} API, which allows you to access{" "} In this BitNode you will be able to access the {FactionNames.Bladeburners} API, which allows you to access{" "}
{FactionNames.Bladeburners} {FactionNames.Bladeburners}
functionality through Netscript. Furthermore: <br /> functionality through Netscript.
<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
<br /> <br />
<br /> <br />
Destroying this BitNode will give you Source-File 7, or if you already have this Source-File it will upgrade its 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 /> <br />
You start with $250 million You start with $250 million
<br /> <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 You start with a WSE membership and access to the TIX API
<br /> <br />
You are able to short stocks and place different types of orders (limit/stop) You are able to short stocks and place different types of orders (limit/stop)
<br /> <br />
You can immediately donate to factions to gain reputation
<br />
<br /> <br />
Destroying this BitNode will give you Source-File 8, or if you already have this Source-File it will upgrade its 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: 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. which can be spent on a variety of different upgrades.
<br /> <br />
<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 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: level up to a maximum of 3. This Source-File grants the following benefits:
<br /> <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 1. Grafting: Visit VitaLife in New Tokyo to be able to obtain Augmentations without needing to install
<br /> <br />
2. Duplicate Sleeves: Duplicate your consciousness into Synthoids, allowing you to perform different tasks 2. Duplicate Sleeves: Duplicate your consciousness into Synthoids, allowing you to perform different tasks
synchronously 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
<br /> <br />
<br /> <br />
Destroying this BitNode will give you Source-File 10, or if you already have this Source-File it will upgrade its 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. world is slowly crumbling in the middle of the biggest economic crisis of all time.
<br /> <br />
<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 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 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 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. other. Find her in {CityName.Chongqing} and gain her trust.
<br /> <br />
<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 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 level up to a maximum of 3. This Source-File lets the {FactionNames.ChurchOfTheMachineGod} appear in other
BitNodes. 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 { export const defaultMultipliers: IBitNodeMultipliers = {
if (p.bitNodeN == null) { HackingLevelMultiplier: 1,
p.bitNodeN = 1; StrengthLevelMultiplier: 1,
} DefenseLevelMultiplier: 1,
for (const mult of Object.keys(BitNodeMultipliers)) { DexterityLevelMultiplier: 1,
if (BitNodeMultipliers.hasOwnProperty(mult)) { AgilityLevelMultiplier: 1,
BitNodeMultipliers[mult] = 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: { case 12: {
//The Recursion const inc = Math.pow(1.02, lvl);
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 dec = 1 / inc; const dec = 1 / inc;
// Multiplier for number of augs needed for Daedalus increases return Object.assign(mults, {
// up to a maximum of 1.34, which results in 40 Augs required DaedalusAugsRequirement: Math.floor(Math.min(mults.DaedalusAugsRequirement + inc, 40)),
BitNodeMultipliers.DaedalusAugsRequirement = Math.min(inc, 1.34);
BitNodeMultipliers.HackingLevelMultiplier = dec; HackingLevelMultiplier: dec,
BitNodeMultipliers.StrengthLevelMultiplier = dec; StrengthLevelMultiplier: dec,
BitNodeMultipliers.DefenseLevelMultiplier = dec; DefenseLevelMultiplier: dec,
BitNodeMultipliers.DexterityLevelMultiplier = dec; DexterityLevelMultiplier: dec,
BitNodeMultipliers.AgilityLevelMultiplier = dec; AgilityLevelMultiplier: dec,
BitNodeMultipliers.CharismaLevelMultiplier = dec; CharismaLevelMultiplier: dec,
BitNodeMultipliers.ServerMaxMoney = dec; ServerMaxMoney: dec,
BitNodeMultipliers.ServerStartingMoney = dec; ServerStartingMoney: dec,
BitNodeMultipliers.ServerGrowthRate = dec; ServerGrowthRate: dec,
BitNodeMultipliers.ServerWeakenRate = dec; ServerWeakenRate: dec,
//Does not scale, otherwise security might start at 300+ //Does not scale, otherwise security might start at 300+
BitNodeMultipliers.ServerStartingSecurity = 1.5; ServerStartingSecurity: 1.5,
BitNodeMultipliers.HomeComputerRamCost = inc; HomeComputerRamCost: inc,
BitNodeMultipliers.PurchasedServerCost = inc; PurchasedServerCost: inc,
BitNodeMultipliers.PurchasedServerLimit = dec; PurchasedServerLimit: dec,
BitNodeMultipliers.PurchasedServerMaxRam = dec; PurchasedServerMaxRam: dec,
BitNodeMultipliers.PurchasedServerSoftcap = inc; PurchasedServerSoftcap: inc,
BitNodeMultipliers.ManualHackMoney = dec; ManualHackMoney: dec,
BitNodeMultipliers.ScriptHackMoney = dec; ScriptHackMoney: dec,
BitNodeMultipliers.CompanyWorkMoney = dec; CompanyWorkMoney: dec,
BitNodeMultipliers.CrimeMoney = dec; CrimeMoney: dec,
BitNodeMultipliers.HacknetNodeMoney = dec; HacknetNodeMoney: dec,
BitNodeMultipliers.CodingContractMoney = dec; CodingContractMoney: dec,
BitNodeMultipliers.CompanyWorkExpGain = dec; CompanyWorkExpGain: dec,
BitNodeMultipliers.ClassGymExpGain = dec; ClassGymExpGain: dec,
BitNodeMultipliers.FactionWorkExpGain = dec; FactionWorkExpGain: dec,
BitNodeMultipliers.HackExpGain = dec; HackExpGain: dec,
BitNodeMultipliers.CrimeExpGain = dec; CrimeExpGain: dec,
BitNodeMultipliers.FactionWorkRepGain = dec; FactionWorkRepGain: dec,
BitNodeMultipliers.FactionPassiveRepGain = dec; FactionPassiveRepGain: dec,
BitNodeMultipliers.RepToDonateToFaction = inc; RepToDonateToFaction: inc,
BitNodeMultipliers.AugmentationRepCost = inc; AugmentationRepCost: inc,
BitNodeMultipliers.AugmentationMoneyCost = inc; AugmentationMoneyCost: inc,
BitNodeMultipliers.InfiltrationMoney = dec; InfiltrationMoney: dec,
BitNodeMultipliers.InfiltrationRep = dec; InfiltrationRep: dec,
BitNodeMultipliers.FourSigmaMarketDataCost = inc; FourSigmaMarketDataCost: inc,
BitNodeMultipliers.FourSigmaMarketDataApiCost = inc; FourSigmaMarketDataApiCost: inc,
BitNodeMultipliers.CorporationValuation = dec; CorporationValuation: dec,
BitNodeMultipliers.BladeburnerRank = dec; BladeburnerRank: dec,
BitNodeMultipliers.BladeburnerSkillCost = inc; BladeburnerSkillCost: inc,
BitNodeMultipliers.StaneksGiftPowerMultiplier = inc; StaneksGiftPowerMultiplier: inc,
BitNodeMultipliers.StaneksGiftExtraSize = inc; StaneksGiftExtraSize: inc,
BitNodeMultipliers.GangSoftcap = 0.8; GangSoftcap: 0.8,
BitNodeMultipliers.CorporationSoftCap = 0.8; CorporationSoftCap: 0.8,
BitNodeMultipliers.WorldDaemonDifficulty = inc; WorldDaemonDifficulty: inc,
BitNodeMultipliers.GangUniqueAugs = dec; GangUniqueAugs: dec,
break; });
} }
case 13: { case 13: {
BitNodeMultipliers.PurchasedServerSoftcap = 1.6; return Object.assign(mults, {
PurchasedServerSoftcap: 1.6,
BitNodeMultipliers.HackingLevelMultiplier = 0.25; HackingLevelMultiplier: 0.25,
BitNodeMultipliers.StrengthLevelMultiplier = 0.7; StrengthLevelMultiplier: 0.7,
BitNodeMultipliers.DefenseLevelMultiplier = 0.7; DefenseLevelMultiplier: 0.7,
BitNodeMultipliers.DexterityLevelMultiplier = 0.7; DexterityLevelMultiplier: 0.7,
BitNodeMultipliers.AgilityLevelMultiplier = 0.7; AgilityLevelMultiplier: 0.7,
BitNodeMultipliers.ServerMaxMoney = 0.45; ServerMaxMoney: 0.45,
BitNodeMultipliers.ServerStartingMoney = 0.75; ServerStartingMoney: 0.75,
BitNodeMultipliers.ServerStartingSecurity = 3; ServerStartingSecurity: 3,
BitNodeMultipliers.ScriptHackMoney = 0.2; ScriptHackMoney: 0.2,
BitNodeMultipliers.CompanyWorkMoney = 0.4; CompanyWorkMoney: 0.4,
BitNodeMultipliers.CrimeMoney = 0.4; CrimeMoney: 0.4,
BitNodeMultipliers.HacknetNodeMoney = 0.4; HacknetNodeMoney: 0.4,
BitNodeMultipliers.CodingContractMoney = 0.4; CodingContractMoney: 0.4,
BitNodeMultipliers.CompanyWorkExpGain = 0.5; CompanyWorkExpGain: 0.5,
BitNodeMultipliers.ClassGymExpGain = 0.5; ClassGymExpGain: 0.5,
BitNodeMultipliers.FactionWorkExpGain = 0.5; FactionWorkExpGain: 0.5,
BitNodeMultipliers.HackExpGain = 0.1; HackExpGain: 0.1,
BitNodeMultipliers.CrimeExpGain = 0.5; CrimeExpGain: 0.5,
BitNodeMultipliers.FactionWorkRepGain = 0.6; FactionWorkRepGain: 0.6,
BitNodeMultipliers.FourSigmaMarketDataCost = 10; FourSigmaMarketDataCost: 10,
BitNodeMultipliers.FourSigmaMarketDataApiCost = 10; FourSigmaMarketDataApiCost: 10,
BitNodeMultipliers.CorporationValuation = 0.001; CorporationValuation: 0.001,
BitNodeMultipliers.BladeburnerRank = 0.45; BladeburnerRank: 0.45,
BitNodeMultipliers.BladeburnerSkillCost = 2; BladeburnerSkillCost: 2,
BitNodeMultipliers.StaneksGiftPowerMultiplier = 2; StaneksGiftPowerMultiplier: 2,
BitNodeMultipliers.StaneksGiftExtraSize = 1; StaneksGiftExtraSize: 1,
BitNodeMultipliers.GangSoftcap = 0.3; GangSoftcap: 0.3,
BitNodeMultipliers.CorporationSoftCap = 0.3; CorporationSoftCap: 0.3,
BitNodeMultipliers.WorldDaemonDifficulty = 3; WorldDaemonDifficulty: 3,
BitNodeMultipliers.GangUniqueAugs = 0.1; GangUniqueAugs: 0.1,
break; });
}
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. * 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 * 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... * 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 * 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. * The multipliers that are influenced by current Bitnode progression.
*/ */
// tslint:disable-next-line:variable-name // tslint:disable-next-line:variable-name
export const BitNodeMultipliers: IBitNodeMultipliers = { export const BitNodeMultipliers = defaultMultipliers;
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,
};

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

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

@ -3,6 +3,7 @@ import { IMap } from "../types";
import { FactionNames } from "./data/FactionNames"; import { FactionNames } from "./data/FactionNames";
import { use } from "../ui/Context"; import { use } from "../ui/Context";
import { Option } from "./ui/Option"; import { Option } from "./ui/Option";
import { Typography } from "@mui/material";
interface FactionInfoParams { interface FactionInfoParams {
infoText?: JSX.Element; infoText?: JSX.Element;
@ -511,51 +512,17 @@ export const FactionInfos: IMap<FactionInfo> = {
); );
}, },
}), }),
// prettier-ignore [FactionNames.ShadowsOfAnarchy]: new FactionInfo({
[FactionNames.Infiltrators]: new FactionInfo({ infoText: (
infoText:(<> <>
{".,=,.==.²=.'=,.=░.==.:=,,▄▓▓▓▓▓▓▓▓▓╬░.==.²!.,=,.=⌐.==.!=,.=,.==.²=.,=,.==.²=.!=,"}<br /> The government is ruled by the corporations that we have allowed to consume it. To release the world from its
{"'¡»⌐^»∩^:»^:=^`=∩':+]▄▄▓▓▓▓▓▓▓▓▓▓▓▓▓▒^»∩^»=^:»^²»⌐^»∩^:=^`»^`»∩^:»^¡»^`»⌐'»÷'\»'"}<br /> shackles, the gods grant us their strength.
{"░░_|░_'░⌐_░∩`|▒▄▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░░`,░``░∩`»░_|░¬`░⌐_░░`'PHYZ|C@L`»░_!░⌐`░░_|"}<br /> </>
{".,=,.»\.²=,,[▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓█▓▒╦╔░¡_²².,=,,=:.==_!²,,=,_==,W@Z,=,,=\.!=./²,"}<br /> ),
{"'`»^^»∩':»')▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓████▒▐▒Ü▒^»=^¡»^^»⌐'»∩':»^`»^'=^:=^H3R3^»∩^:»':=^"}<br /> special: true,
{"░∩`:░_'░⌐`░▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓█████ÑÜP╩Ü╙░`_░∩`|░`:░_`░∩`|∩`:░_`░⌐_░░`|░`,░⌐`░∩`»"}<br /> keepOnInstall: true,
{".,=,,==_²=,▀▓▓▓▓▓▓▓▓▓▓▓▓▓▓██▌Ñ▓██ÑÜÜ▒Ü▒░_»=,,=,.==_»=,!=,,=,_==,»░,,=,,==_»=,!=,"}<br /> assignment: (): React.ReactElement => {
{"'`=^^=∩^:»^:»└'µ╫█████████╬▓▓▓██Ññ▒NÜÜ▒╠^»=':=^^=∩^»=^:=^`=⌐^»»':»`¡»^`=∩^:=^\»'"}<br /> return <Typography>{FactionNames.ShadowsOfAnarchy} can only gain reputation by infiltrating.</Typography>;
{"░░`:░_,░⌐`=∩`|░_¡█╬▀███▓╬╬▀▓▓▀Ñ▒ÄÜ╟ñÜÜ╠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,
}),
}; };

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

@ -167,7 +167,7 @@ export function AugmentationsPage(props: IProps): React.ReactElement {
); );
} }
const multiplierComponent = const multiplierComponent =
props.faction.name !== FactionNames.Infiltrators ? ( props.faction.name !== FactionNames.ShadowsOfAnarchy ? (
<Typography> <Typography>
Price multiplier: x {numeralWrapper.formatMultiplier(getGenericAugmentationPriceMultiplier())} Price multiplier: x {numeralWrapper.formatMultiplier(getGenericAugmentationPriceMultiplier())}
</Typography> </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 { IPlayer } from "../../PersonObjects/IPlayer";
import { BitNodeMultipliers } from "../../BitNode/BitNodeMultipliers"; import { BitNodeMultipliers } from "../../BitNode/BitNodeMultipliers";
import { LocationsMetadata } from "../../Locations/data/LocationsMetadata"; import { LocationsMetadata } from "../../Locations/data/LocationsMetadata";
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
import { Faction } from "../../Faction/Faction";
export function calculateSellInformationCashReward( export function calculateSellInformationCashReward(
player: IPlayer, player: IPlayer,
@ -15,7 +17,7 @@ export function calculateSellInformationCashReward(
Math.pow(difficulty, 3) * Math.pow(difficulty, 3) *
3e3 * 3e3 *
levelBonus * levelBonus *
player.infiltration_sell_mult * (player.hasAugmentation(AugmentationNames.WKSharmonizer) ? 1.5 : 1) *
BitNodeMultipliers.InfiltrationMoney BitNodeMultipliers.InfiltrationMoney
); );
} }
@ -33,17 +35,17 @@ export function calculateTradeInformationRepReward(
Math.pow(difficulty, 3) * Math.pow(difficulty, 3) *
3e3 * 3e3 *
levelBonus * levelBonus *
player.infiltration_sell_mult * (player.hasAugmentation(AugmentationNames.WKSharmonizer) ? 1.5 : 1) *
BitNodeMultipliers.InfiltrationMoney 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 maxStartingSecurityLevel = LocationsMetadata.reduce((acc, data): number => {
const startingSecurityLevel = data.infiltrationData?.startingSecurityLevel || 0; const startingSecurityLevel = data.infiltrationData?.startingSecurityLevel || 0;
return acc > startingSecurityLevel ? acc : startingSecurityLevel; return acc > startingSecurityLevel ? acc : startingSecurityLevel;
}, 0); }, 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 timer = difficulty.timer;
const [answer] = useState(makeAnswer(difficulty)); const [answer] = useState(makeAnswer(difficulty));
const [guess, setGuess] = useState(""); const [guess, setGuess] = useState("");
const hasAugment = Player.hasAugmentation(AugmentationNames.RearViewMirrorShoulderAttachment, true); const hasAugment = Player.hasAugmentation(AugmentationNames.ChaosOfDionysus, true);
function press(this: Document, event: KeyboardEvent): void { function press(this: Document, event: KeyboardEvent): void {
event.preventDefault(); event.preventDefault();

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

@ -47,7 +47,7 @@ export function BribeGame(props: IMinigameProps): React.ReactElement {
let upColor = defaultColor; let upColor = defaultColor;
let downColor = defaultColor; let downColor = defaultColor;
let choiceColor = defaultColor; let choiceColor = defaultColor;
const hasAugment = Player.hasAugmentation(AugmentationNames.KyberCrystalInjection, true); const hasAugment = Player.hasAugmentation(AugmentationNames.BeautyOfAphrodite, true);
if (hasAugment) { if (hasAugment) {
const upIndex = index + 1 >= choices.length ? 0 : index + 1; 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 timer = difficulty.timer;
const [code] = useState(generateCode(difficulty)); const [code] = useState(generateCode(difficulty));
const [index, setIndex] = useState(0); const [index, setIndex] = useState(0);
const hasAugment = Player.hasAugmentation(AugmentationNames.DyslexiaModule, true); const hasAugment = Player.hasAugmentation(AugmentationNames.TrickeryOfHermes, true);
function press(this: Document, event: KeyboardEvent): void { function press(this: Document, event: KeyboardEvent): void {
event.preventDefault(); event.preventDefault();

@ -40,7 +40,7 @@ export function Cyberpunk2077Game(props: IMinigameProps): React.ReactElement {
const [currentAnswerIndex, setCurrentAnswerIndex] = useState(0); const [currentAnswerIndex, setCurrentAnswerIndex] = useState(0);
const [pos, setPos] = useState([0, 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 { function press(this: Document, event: KeyboardEvent): void {
event.preventDefault(); event.preventDefault();
const move = [0, 0]; const move = [0, 0];

@ -13,6 +13,7 @@ import { MinesweeperGame } from "./MinesweeperGame";
import { WireCuttingGame } from "./WireCuttingGame"; import { WireCuttingGame } from "./WireCuttingGame";
import { Victory } from "./Victory"; import { Victory } from "./Victory";
import Typography from "@mui/material/Typography"; import Typography from "@mui/material/Typography";
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
interface IProps { interface IProps {
StartingDifficulty: number; StartingDifficulty: number;
@ -93,7 +94,7 @@ export function Game(props: IProps): React.ReactElement {
// it's clear they're not meant to // it's clear they're not meant to
const damage = options?.automated const damage = options?.automated
? player.hp ? player.hp
: props.StartingDifficulty * 3 * player.infiltration_damage_reduction_mult; : props.StartingDifficulty * 3 * (player.hasAugmentation(AugmentationNames.WKSharmonizer) ? 0.5 : 1);
if (player.takeDamage(damage)) { if (player.takeDamage(damage)) {
router.toCity(); router.toCity();
return; return;
@ -112,7 +113,16 @@ export function Game(props: IProps): React.ReactElement {
stageComponent = <Countdown onFinish={() => setStage(Stage.Minigame)} />; stageComponent = <Countdown onFinish={() => setStage(Stage.Minigame)} />;
break; break;
case Stage.Minigame: { 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} />; stageComponent = <MiniGame onSuccess={success} onFailure={failure} difficulty={props.Difficulty + level / 50} />;
break; break;
} }

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

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

@ -40,7 +40,7 @@ export function SlashGame(props: IMinigameProps): React.ReactElement {
props.onSuccess(); 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 phaseZeroTime = Math.random() * 3250 + 1500 - (250 + difficulty.window);
const phaseOneTime = 250; const phaseOneTime = 250;
const timeUntilAttacking = phaseZeroTime + phaseOneTime; const timeUntilAttacking = phaseZeroTime + phaseOneTime;

@ -15,6 +15,7 @@ import {
calculateSellInformationCashReward, calculateSellInformationCashReward,
calculateTradeInformationRepReward, calculateTradeInformationRepReward,
} from "../formulas/victory"; } from "../formulas/victory";
import { inviteToFaction } from "../../Faction/FactionHelpers";
interface IProps { interface IProps {
StartingDifficulty: number; StartingDifficulty: number;
@ -33,12 +34,12 @@ export function Victory(props: IProps): React.ReactElement {
router.toCity(); router.toCity();
} }
const soa = Factions[FactionNames.ShadowsOfAnarchy];
const repGain = calculateTradeInformationRepReward(player, props.Reward, props.MaxLevel, props.StartingDifficulty); const repGain = calculateTradeInformationRepReward(player, props.Reward, props.MaxLevel, props.StartingDifficulty);
const moneyGain = calculateSellInformationCashReward(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 = player.factions.includes(FactionNames.ShadowsOfAnarchy);
const isMemberOfInfiltrators = infiltratorFaction && infiltratorFaction.isMember;
function sell(): void { function sell(): void {
handleInfiltrators(); handleInfiltrators();
@ -58,9 +59,9 @@ export function Victory(props: IProps): React.ReactElement {
} }
function handleInfiltrators(): void { function handleInfiltrators(): void {
player.hasCompletedAnInfiltration = true; inviteToFaction(Factions[FactionNames.ShadowsOfAnarchy]);
if (isMemberOfInfiltrators) { if (isMemberOfInfiltrators) {
infiltratorFaction.playerReputation += infiltrationRepGain; soa.playerReputation += infiltrationRepGain;
} }
} }
@ -75,7 +76,7 @@ export function Victory(props: IProps): React.ReactElement {
You{" "} You{" "}
{isMemberOfInfiltrators ? ( {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 [wires] = useState(generateWires(difficulty));
const [cutWires, setCutWires] = useState(new Array(wires.length).fill(false)); const [cutWires, setCutWires] = useState(new Array(wires.length).fill(false));
const [questions] = useState(generateQuestion(wires, difficulty)); const [questions] = useState(generateQuestion(wires, difficulty));
const hasAugment = Player.hasAugmentation(AugmentationNames.SecurityWireContacts, true); const hasAugment = Player.hasAugmentation(AugmentationNames.KnowledgeOfApollo, true);
function checkWire(wireNum: number): boolean { function checkWire(wireNum: number): boolean {
return !questions.some((q) => q.shouldCut(wires[wireNum - 1], wireNum - 1)); return questions.some((q) => q.shouldCut(wires[wireNum - 1], wireNum - 1));
} }
useEffect(() => { useEffect(() => {
@ -110,9 +110,15 @@ export function WireCuttingGame(props: IMinigameProps): React.ReactElement {
<Typography key={i}>{question.toString()}</Typography> <Typography key={i}>{question.toString()}</Typography>
))} ))}
<Typography> <Typography>
{new Array(wires.length).fill(0).map((_, i) => ( {new Array(wires.length).fill(0).map((_, i) => {
<span key={i}>&nbsp;{i + 1}&nbsp;&nbsp;&nbsp;&nbsp;</span> const isCorrectWire = checkWire(i + 1);
))} const color = hasAugment && !isCorrectWire ? Settings.theme.disabled : Settings.theme.primary;
return (
<span key={i} style={{ color: color }}>
&nbsp;{i + 1}&nbsp;&nbsp;&nbsp;&nbsp;
</span>
);
})}
</Typography> </Typography>
{new Array(8).fill(0).map((_, i) => ( {new Array(8).fill(0).map((_, i) => (
<div key={i}> <div key={i}>

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

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

@ -730,7 +730,22 @@ export function NetscriptCorporation(
const divisionName = helper.string("hireEmployee", "divisionName", _divisionName); const divisionName = helper.string("hireEmployee", "divisionName", _divisionName);
const cityName = helper.city("hireEmployee", "cityName", _cityName); const cityName = helper.city("hireEmployee", "cityName", _cityName);
const office = getOffice(divisionName, 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 { upgradeOfficeSize: function (_divisionName: unknown, _cityName: unknown, _size: unknown): void {
checkAccess("upgradeOfficeSize", 8); checkAccess("upgradeOfficeSize", 8);

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

@ -13,6 +13,10 @@ import {
} from "../ScriptEditor/NetscriptDefinitions"; } from "../ScriptEditor/NetscriptDefinitions";
import { AugmentationNames } from "../Augmentation/data/AugmentationNames"; import { AugmentationNames } from "../Augmentation/data/AugmentationNames";
import { NetscriptContext, InternalAPI } from "../Netscript/APIWrapper"; 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( export function NetscriptStanek(
player: IPlayer, player: IPlayer,
@ -109,5 +113,29 @@ export function NetscriptStanek(
checkStanekAPIAccess("removeFragment"); checkStanekAPIAccess("removeFragment");
return staneksGift.delete(rootX, rootY); 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 { purchase4SMarketData: function (): boolean {
updateRam("purchase4SMarketData"); updateRam("purchase4SMarketData");
checkTixApiAccess("purchase4SMarketData");
if (player.has4SData) { if (player.has4SData) {
workerScript.log("stock.purchase4SMarketData", () => "Already purchased 4S Market Data."); workerScript.log("stock.purchase4SMarketData", () => "Already purchased 4S Market Data.");

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

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

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

@ -159,7 +159,6 @@ export function prestigeAugmentation(this: PlayerObject): void {
this.workChaExpGained = 0; this.workChaExpGained = 0;
this.workRepGained = 0; this.workRepGained = 0;
this.workMoneyGained = 0; this.workMoneyGained = 0;
this.hasCompletedAnInfiltration = false;
this.timeWorked = 0; this.timeWorked = 0;
@ -312,13 +311,6 @@ export function resetMultipliers(this: IPlayer): void {
this.bladeburner_stamina_gain_mult = 1; this.bladeburner_stamina_gain_mult = 1;
this.bladeburner_analysis_mult = 1; this.bladeburner_analysis_mult = 1;
this.bladeburner_success_chance_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 { export function hasProgram(this: IPlayer, programName: string): boolean {
@ -1877,13 +1869,12 @@ export function getNextCompanyPosition(
export function quitJob(this: IPlayer, company: string): void { export function quitJob(this: IPlayer, company: string): void {
if (this.isWorking == true && this.workType.includes("Working for Company") && this.companyName == company) { if (this.isWorking == true && this.workType.includes("Working for Company") && this.companyName == company) {
this.isWorking = false; this.finishWork(true);
this.companyName = "";
}
if (this.companyName === company) {
this.companyName = "";
} }
delete this.jobs[company]; 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; return allCompanies.includes(companyName) && getCompanyRep(companyName) > repNeeded;
} }
//Infiltrators
const InfiltratorsFac = Factions[FactionNames.Infiltrators];
if (this.hasCompletedAnInfiltration && !InfiltratorsFac.isMember && !InfiltratorsFac.alreadyInvited) {
invitedFactions.push(InfiltratorsFac);
}
//Illuminati //Illuminati
const illuminatiFac = Factions[FactionNames.Illuminati]; const illuminatiFac = Factions[FactionNames.Illuminati];
if ( if (
@ -2180,7 +2165,7 @@ export function checkForFactionInvitations(this: IPlayer): Faction[] {
!daedalusFac.isBanned && !daedalusFac.isBanned &&
!daedalusFac.isMember && !daedalusFac.isMember &&
!daedalusFac.alreadyInvited && !daedalusFac.alreadyInvited &&
numAugmentations >= Math.round(30 * BitNodeMultipliers.DaedalusAugsRequirement) && numAugmentations >= BitNodeMultipliers.DaedalusAugsRequirement &&
this.money >= 100000000000 && this.money >= 100000000000 &&
(this.hacking >= 2500 || (this.hacking >= 2500 ||
(this.strength >= 1500 && this.defense >= 1500 && this.dexterity >= 1500 && this.agility >= 1500)) (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[] { function possibleFactions(player: IPlayer, sleeve: Sleeve): string[] {
// Array of all factions that other sleeves are working for // Array of all factions that other sleeves are working for
const forbiddenFactions = [FactionNames.Bladeburners as string, FactionNames.Infiltrators as string]; const forbiddenFactions = [FactionNames.Bladeburners as string, FactionNames.ShadowsOfAnarchy as string];
if (player.gang) { if (player.gang) {
forbiddenFactions.push(player.gang.facName); forbiddenFactions.push(player.gang.facName);
} }

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

@ -23,6 +23,9 @@ import { LocationName } from "./Locations/data/LocationNames";
import { SxProps } from "@mui/system"; import { SxProps } from "@mui/system";
import { PlayerObject } from "./PersonObjects/Player/PlayerObject"; import { PlayerObject } from "./PersonObjects/Player/PlayerObject";
import { pushGameSaved } from "./Electron"; import { pushGameSaved } from "./Electron";
import { defaultMonacoTheme } from "./ScriptEditor/ui/themes";
import { FactionNames } from "./Faction/data/FactionNames";
import { Faction } from "./Faction/Faction";
/* SaveObject.js /* SaveObject.js
* Defines the object used to save/load games * Defines the object used to save/load games
@ -394,8 +397,11 @@ function evaluateVersionCompatibility(ver: string | number): void {
delete anyPlayer.resleeves; delete anyPlayer.resleeves;
} }
} }
if (ver < 14) { if (ver < 15) {
delete (Settings as any).EditorTheme; (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. * @returns The fragment at [rootX, rootY], if any.
*/ */
removeFragment(rootX: number, rootY: number): boolean; 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 { export interface InfiltrationReward {
tradeRep: number; tradeRep: number;
sellCash: number; sellCash: number;
infiltratorRep: number; SoARep: number;
} }
export interface InfiltrationLocation { export interface InfiltrationLocation {
@ -4280,24 +4290,6 @@ export interface InfiltrationLocation {
* @public * @public
*/ */
interface Infiltration { 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. * Get all locations that can be infiltrated.
* @remarks * @remarks
@ -4305,15 +4297,15 @@ interface Infiltration {
* *
* @returns all locations that can be infiltrated. * @returns all locations that can be infiltrated.
*/ */
getLocations(): any[]; getPossibleLocations(): string[];
/** /**
* Get all infiltrations with difficulty, location and rewards. * Get all infiltrations with difficulty, location and rewards.
* @remarks * @remarks
* RAM cost: 15 GB * 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"); terminal.error("Invalid path. Failed to change directories");
return; return;
} }
if (terminal.cwd().length > 1 && dir === "..") {
terminal.setcwd(evaledDir);
return;
}
const server = player.getCurrentServer(); const server = player.getCurrentServer();
if (!containsFiles(server, evaledDir)) { if (!containsFiles(server, evaledDir)) {

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

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

@ -1306,4 +1306,154 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [
return parseInt(ans, 10) === HammingDecode(data); 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 { Money } from "./React/Money";
import { StatsRow } from "./React/StatsRow"; import { StatsRow } from "./React/StatsRow";
import { StatsTable } from "./React/StatsTable"; import { StatsTable } from "./React/StatsTable";
import { FactionNames } from "../Faction/data/FactionNames";
interface EmployersModalProps { interface EmployersModalProps {
open: boolean; open: boolean;
@ -83,13 +82,12 @@ function CurrentBitNode(): React.ReactElement {
const player = use.Player(); const player = use.Player();
if (player.sourceFiles.length > 0) { if (player.sourceFiles.length > 0) {
const index = "BitNode" + player.bitNodeN; const index = "BitNode" + player.bitNodeN;
const currentSourceFile = player.sourceFiles.find((sourceFile) => sourceFile.n == player.bitNodeN); const lvl = player.sourceFileLvl(player.bitNodeN) + 1;
const lvl = currentSourceFile ? currentSourceFile.lvl : 0;
return ( return (
<Box> <Box>
<Paper sx={{ p: 1 }}> <Paper sx={{ p: 1 }}>
<Typography variant="h5"> <Typography variant="h5">
BitNode {player.bitNodeN}: {BitNodes[index].name} (Level {lvl + 1}) BitNode {player.bitNodeN}: {BitNodes[index].name} (Level {lvl})
</Typography> </Typography>
<Typography sx={{ whiteSpace: "pre-wrap", overflowWrap: "break-word" }}>{BitNodes[index].info}</Typography> <Typography sx={{ whiteSpace: "pre-wrap", overflowWrap: "break-word" }}>{BitNodes[index].info}</Typography>
</Paper> </Paper>
@ -496,18 +494,6 @@ export function CharacterStats(): React.ReactElement {
noMargin 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>
</Box> </Box>
</Paper> </Paper>

@ -44,7 +44,7 @@ import { CorporationRoot } from "../Corporation/ui/CorporationRoot";
import { InfiltrationRoot } from "../Infiltration/ui/InfiltrationRoot"; import { InfiltrationRoot } from "../Infiltration/ui/InfiltrationRoot";
import { GraftingRoot } from "../PersonObjects/Grafting/ui/GraftingRoot"; import { GraftingRoot } from "../PersonObjects/Grafting/ui/GraftingRoot";
import { WorkInProgressRoot } from "./WorkInProgressRoot"; import { WorkInProgressRoot } from "./WorkInProgressRoot";
import { GameOptionsRoot } from "./React/GameOptionsRoot"; import { GameOptionsRoot } from "../GameOptions/ui/GameOptionsRoot";
import { SleeveRoot } from "../PersonObjects/Sleeve/ui/SleeveRoot"; import { SleeveRoot } from "../PersonObjects/Sleeve/ui/SleeveRoot";
import { HacknetRoot } from "../Hacknet/ui/HacknetRoot"; import { HacknetRoot } from "../Hacknet/ui/HacknetRoot";
import { GenericLocation } from "../Locations/ui/GenericLocation"; 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> </Typography>
<Box position="absolute" right={0}> <Box position="absolute" right={0}>
{!workerScripts.has(script.pid) && <Button onClick={run}>Run</Button>} {!workerScripts.has(script.pid) && (
{workerScripts.has(script.pid) && <Button onClick={kill}>Kill</Button>} <Button onClick={run} onTouchEnd={run}>
<Button onClick={minimize}>{minimized ? "\u{1F5D6}" : "\u{1F5D5}"}</Button> Run
<Button onClick={props.onClose}>Close</Button> </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>
</Box> </Box>
</Paper> </Paper>

@ -130,7 +130,14 @@ export function Overview({ children, mode }: IProps): React.ReactElement {
size="small" size="small"
className={classes.visibilityToggle} 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> </Button>
</Box> </Box>
</Box> </Box>

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

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

@ -93,15 +93,6 @@ module.exports = (env, argv) => {
new webpack.DefinePlugin({ new webpack.DefinePlugin({
"process.env.NODE_ENV": isDevelopment ? '"development"' : '"production"', "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 HtmlWebpackPlugin(htmlConfig),
new MiniCssExtractPlugin({ new MiniCssExtractPlugin({
filename: "[name].css", filename: "[name].css",