Merge branch 'dev' of github.com:danielyxie/bitburner into dev

This commit is contained in:
Olivier Gagnon 2022-09-27 17:37:36 -04:00
commit 444c74ccd9
359 changed files with 4172 additions and 6977 deletions

@ -3,22 +3,22 @@
## In General ## In General
The game is made better because the community as a whole speaks up about The game is made better because the community as a whole speaks up about
ways to improve the game. Here's some of the ways you can make your voice ways to improve the game. Here are some of the ways you can make your voice
heard: heard:
- [Discord](https://discord.gg/XKEGvHqVr3) - [Discord](https://discord.gg/XKEGvHqVr3).
There is a dedicated Discord instance set up for more free-form chats There is a dedicated Discord instance set up for more free-form chats
between all members of the community. Regular players, heavy scripters, between all members of the community. Regular players, heavy scripters,
Bitburner contributors, and everyone in between can be found on the Bitburner contributors, and everyone in between can be found on the
server. server.
- [Github Issues](https://github.com/danielyxie/bitburner/issues) - [Github Issues](https://github.com/danielyxie/bitburner/issues).
Although the term "issues" can have a negative connotation, they are a Although the term "issues" can have a negative connotation, they are a
means of communicating with the community. A new Issue can be a means of communicating with the community. A new Issue can be an
interesting new feature that you feel would improve the game. It could be interesting new feature that you feel would improve the game. It could be
an unexpected behavior within the game. Or because the game is about an unexpected behavior within the game. Or because the game is about
scripting perhaps there is something that is conflicting with the scripting perhaps there is something that is conflicting with the
browser's Javascript interaction. So please do not be afraid to open a browser's JavaScript interaction. So please do not be afraid to open a
[new issue](https://github.com/danielyxie/bitburner/issues/new). [new Issue](https://github.com/danielyxie/bitburner/issues/new).
## Reporting Bugs ## Reporting Bugs
@ -33,16 +33,17 @@ already been reported as an [Issue](https://github.com/danielyxie/bitburner/issu
#### How to Submit a Good Bug Report #### How to Submit a Good Bug Report
- **Use a clear and descriptive title** for the issue - **Use a clear and descriptive title** for the Issue.
- **State your browser, your browser's version, and your computer's OS** - **State your browser, your browser's version, and your computer's OS.**
- **Attach your save file**, if you think it would help solve the issue - **Attach your save file**, if you think it would help solve the Issue.
Zip your save file first, then attach the zipped save file.
- **Provide instructions on how to reproduce the bug** in as much detail - **Provide instructions on how to reproduce the bug** in as much detail
as possible. If you cannot reliably reproduce the bug, then just try as possible. If you cannot reliably reproduce the bug, then just try
your best to explain what was happening when the bug occurred your best to explain what was happening when the bug occurred.
- **Provide any scripts** that triggered the bug if the issue is Netscript-related - **Provide any scripts** that triggered the bug if the Issue is Netscript-related.
- **Open your browser's Dev Console and report any error-related output** - **Open your browser's Dev Console and report any error-related output**
that may be printed there. The Dev Console can be opened on most modern that may be printed there. The Dev Console can be opened on most modern
browsers by pressing F12 browsers by pressing F12.
## As a Developer ## As a Developer
@ -71,13 +72,13 @@ changes are okay to contribute:
##### Contributions that Will Most Likely Be Accepted ##### Contributions that Will Most Likely Be Accepted
- Bug Fixes - Bug fixes
- Quality-of-Life Changes - Quality-of-life changes
- Adding a new, commonly-requested Netscript function - Adding a new, commonly-requested Netscript function
- Fixing or improving UI elements - Fixing or improving UI elements
- Adding game settings/options - Adding game settings/options
- Adding a new Terminal command - Adding a new Terminal command
- Code Refactors that conform to good/standard practices - Code refactors that conform to good/standard practices
##### Contributions that will not be Accepted without prior approval ##### Contributions that will not be Accepted without prior approval
@ -88,67 +89,108 @@ changes are okay to contribute:
## How to setup fork properly ## How to setup fork properly
Fork and clone the repo Clone and fork the game's repository by using one of these methods: web browser, GitHub
Desktop, or command line.
``` - Web browser. Log in to your GitHub account, navigate to the
# This will add the game original code as a repo in your local copy [game's repository](https://github.com/danielyxie/bitburner), and fork the
$ git remote add danielyxie git@github.com:danielyxie/bitburner.git repository. Refer to
[this page](https://docs.github.com/en/get-started/quickstart/fork-a-repo) for more
detail.
- GitHub Desktop. Click on `File`, then click `Clone repository`. Click on the `URL`
tab and type `danielyxie/bitburner` into the text box for repository URL. Choose
the path where you want to clone the repository and click the `Clone` button.
Refer to [this page](https://docs.github.com/en/desktop/contributing-and-collaborating-using-github-desktop/adding-and-cloning-repositories/cloning-and-forking-repositories-from-github-desktop)
for more detail.
- Command line.
# You can verify you did this right by doing the following command ```sh
$ git remote show # This clones the game's code repository. The output you get might vary.
danielyxie $ git clone https://github.com/danielyxie/bitburner.git
origin Cloning into 'bitburner'...
remote: Enumerating objects: 57072, done.
remote: Counting objects: 100% (404/404), done.
remote: Compressing objects: 100% (205/205), done.
remote: Total 57072 (delta 210), reused 375 (delta 199), pack-reused 56668
Receiving objects: 100% (57072/57072), 339.11 MiB | 5.42 MiB/s, done.
Resolving deltas: 100% (43708/43708), done.
Updating files: 100% (2561/2561), done.
# Then download all the branches from the game. (there might be more branches) # Change to the directory that contains your local copy.
$ git fetch danielyxie $ cd bitburner
From github.com:danielyxie/bitburner
* [new branch] dev -> danielyxie/dev
* [new branch] master -> danielyxie/master
# Makes sure you always start from `danielyxie/dev` to avoid merge conflicts. # The upstream is the repository that contains the game's source code. The
# upstream is also the place where proposed changes are merged into the game.
$ git remote rename origin upstream
Renaming remote references: 100% (8/8), done.
# The origin is your own copy or fork of the game's source code. Assume that
# your fork will be on GitHub. Change "myname" to your GitHub username. Change
# "myfork" to the name of your forked repository.
$ git remote add origin https://github.com/myname/myfork
# Now "origin" is your fork and "upstream" is where changes should be merged.
$ git remote show
origin
upstream
# You can now download all changes and branches from the upstream repository.
# The output you get might vary.
$ git fetch upstream
# Make sure you always start from "upstream/dev" to avoid merge conflicts.
$ git branch
* dev
$ git branch -r
upstream/BN14
upstream/HEAD -> upstream/dev
upstream/dev
upstream/folders
upstream/master
``` ```
## Development Workflow Best Practices ## Development Workflow Best Practices
- Work in a new branch forked from the `dev` branch to isolate your new code - Work in a new branch forked from the `dev` branch to isolate your new code.
- Keep code-changes on a branch as small as possible. This makes it easier for code review. Each branch should be its own independent feature. - Keep code-changes on a branch as small as possible. This makes it easier for code review. Each branch should be its own independent feature.
- Regularly rebase your branch against `dev` to make sure you have the latest updates pulled. - Regularly rebase your branch against `dev` to make sure you have the latest updates pulled.
- When merging, always merge your branch into `dev`. When releasing a new update, then merge `dev` into `master` - When merging, always merge your branch into `dev`. When releasing a new update, merge `dev` into `master`.
## Running locally. ## Running locally
Install Install
- `npm` (maybe via `nvm`) - `npm` (maybe via `nvm`)
- Github Desktop (windows only) - Github Desktop (Windows only)
- Visual Studio code (optional) - Visual Studio Code (optional)
Inside the root of the repo run Inside the root of the repository run:
`npm install` to install all the dependencies
`npm run start:dev` to launch the game in dev mode. - `npm install` to install all the dependencies; and
- `npm run start:dev` to launch the game in dev mode.
After that you can open any browser and navigate to `localhost:8000` and play the game. After that you can open any browser and navigate to `localhost:8000` and play the game.
Saving a file will reload the game automatically. Saving a file will reload the game automatically.
### How to build the electron app ### How to build the electron app
Tested on Node v16.13.1 (LTS) on Windows Tested on Node v16.13.1 (LTS) on Windows.
These steps only work in a bash-like environment, like MinGW for Windows. These steps only work in a Bash-like environment, like MinGW for Windows.
```sh ```sh
# Install the main game dependencies & build the app in debug mode # Install the main game dependencies & build the app in debug mode.
npm install $ npm install
npm run build:dev $ npm run build:dev
# Use electron-packager to build the app to the .build/ folder # Use electron-packager to build the app to the .build/ folder.
npm run electron $ npm run electron
# When launching the .exe directly, you'll need the steam_appid.txt file in the root # When launching the .exe directly, you'll need the steam_appid.txt file in the root.
# If not using windows, change this line accordingly # If not using Windows, change this line accordingly.
cp .build/bitburner-win32-x64/resources/app/steam_appid.txt .build/bitburner-win32-x64/steam_appid.txt $ cp .build/bitburner-win32-x64/resources/app/steam_appid.txt .build/bitburner-win32-x64/steam_appid.txt
# And run the game... # And run the game...
.build/bitburner-win32-x64/bitburner.exe $ .build/bitburner-win32-x64/bitburner.exe
``` ```
### Submitting a Pull Request ### Submitting a Pull Request
@ -156,15 +198,15 @@ cp .build/bitburner-win32-x64/resources/app/steam_appid.txt .build/bitburner-win
When submitting a pull request with your code contributions, please abide by When submitting a pull request with your code contributions, please abide by
the following rules: the following rules:
- Work in a branch forked from `dev` to isolate the new code - Work in a branch forked from `dev` to isolate the new code.
- Ensure you have latest from the [game's main - Ensure you have the latest from the [game's main
repository](danielyxie/bitburner@dev) repository](../../../tree/dev).
- Rebase your branch if necessary - Rebase your branch if necessary.
- Run the game locally to test out your changes - Run the game locally to test out your changes.
- When submitting the pull request, make sure that the base fork is - When submitting the pull request, make sure that the base fork is
_danielyxie/bitburner_ and the base is _dev_. _danielyxie/bitburner_ and the base is _dev_.
- If your changes affect the game's UI, attach some screenshots or GIFs showing - If your changes affect the game's UI, attach some screenshots or GIFs showing
the changes to the UI the changes to the UI.
- If your changes affect Netscript, provide some - If your changes affect Netscript, provide some
scripts that can be used to test the Netscript changes. scripts that can be used to test the Netscript changes.
- Ensure you have run `npm run lint` to make sure your changes conform to the - Ensure you have run `npm run lint` to make sure your changes conform to the
@ -174,23 +216,23 @@ the following rules:
in the root of the repository. These will be updated as part of official in the root of the repository. These will be updated as part of official
releases. releases.
## As a Documentor ## As a Documenter
To contribute to and view your changes to the BitBurner documentation on [Read The To contribute to and view your changes to the BitBurner documentation on [Read The
Docs](http://bitburner.readthedocs.io/), you will Docs](http://bitburner.readthedocs.io/), you will
need to have Python installed, along with [Sphinx](http://www.sphinx-doc.org). need to have Python installed, along with [Sphinx](http://www.sphinx-doc.org).
To make change to the [in-game documentation](../markdown/bitburner.md), you will need to modify the [TypeScript definitions](../src/ScriptEditor/NetscriptDefinitions.d.ts), not the markdown files. To make change to the [in-game documentation](../markdown/bitburner.md), you will need to modify the [TypeScript definitions](../src/ScriptEditor/NetscriptDefinitions.d.ts), not the Markdown files.
We are using [API Extractor](https://api-extractor.com/pages/tsdoc/doc_comment_syntax/) (tsdoc hints) to generate the markdown doc. Make your changes to the TypeScript definitions and then run `npm run doc`. We are using [API Extractor](https://api-extractor.com/pages/tsdoc/doc_comment_syntax/) (tsdoc hints) to generate the Markdown doc. Make your changes to the TypeScript definitions and then run `npm run doc`.
Before submitting your code for a pull request, please try to follow these Before submitting your code for a pull request, please try to follow these
rules: rules:
- Work in a branch forked from `dev` to isolate the new code - Work in a branch forked from `dev` to isolate the new code.
- Ensure you have latest from the [game's main - Ensure you have the latest from the [game's main
repository](danielyxie/bitburner@dev) repository](../../../tree/dev).
- Rebase your branch if necessary - Rebase your branch if necessary.
- When submitting the pull request, make sure that the base fork is - When submitting the pull request, make sure that the base fork is
_danielyxie/bitburner_ and the base is _dev_. _danielyxie/bitburner_ and the base is _dev_.
- Do not check in any generated files under `doc\`. The documentation is built - Do not check in any generated files under `doc\`. The documentation is built
@ -204,5 +246,5 @@ Update the following
- `package.json` `version` - `package.json` `version`
- `doc/source/conf.py` `version` and `release` - `doc/source/conf.py` `version` and `release`
- `doc/source/changelog.rst` - `doc/source/changelog.rst`
- post to discord - post to Discord
- post to reddit.com/r/Bitburner - post to reddit.com/r/Bitburner

@ -347,18 +347,18 @@ export const achievements: IMap<Achievement> = {
FIRST_HACKNET_NODE: { FIRST_HACKNET_NODE: {
...achievementData["FIRST_HACKNET_NODE"], ...achievementData["FIRST_HACKNET_NODE"],
Icon: "node", Icon: "node",
Condition: () => !hasHacknetServers(Player) && Player.hacknetNodes.length > 0, Condition: () => !hasHacknetServers() && Player.hacknetNodes.length > 0,
}, },
"30_HACKNET_NODE": { "30_HACKNET_NODE": {
...achievementData["30_HACKNET_NODE"], ...achievementData["30_HACKNET_NODE"],
Icon: "hacknet-all", Icon: "hacknet-all",
Condition: () => !hasHacknetServers(Player) && Player.hacknetNodes.length >= 30, Condition: () => !hasHacknetServers() && Player.hacknetNodes.length >= 30,
}, },
MAX_HACKNET_NODE: { MAX_HACKNET_NODE: {
...achievementData["MAX_HACKNET_NODE"], ...achievementData["MAX_HACKNET_NODE"],
Icon: "hacknet-max", Icon: "hacknet-max",
Condition: (): boolean => { Condition: (): boolean => {
if (hasHacknetServers(Player)) return false; if (hasHacknetServers()) return false;
for (const h of Player.hacknetNodes) { for (const h of Player.hacknetNodes) {
if (!(h instanceof HacknetNode)) return false; if (!(h instanceof HacknetNode)) return false;
if ( if (
@ -374,7 +374,7 @@ export const achievements: IMap<Achievement> = {
HACKNET_NODE_10M: { HACKNET_NODE_10M: {
...achievementData["HACKNET_NODE_10M"], ...achievementData["HACKNET_NODE_10M"],
Icon: "hacknet-10m", Icon: "hacknet-10m",
Condition: () => !hasHacknetServers(Player) && Player.moneySourceB.hacknet >= 10e6, Condition: () => !hasHacknetServers() && Player.moneySourceB.hacknet >= 10e6,
}, },
REPUTATION_10M: { REPUTATION_10M: {
...achievementData["REPUTATION_10M"], ...achievementData["REPUTATION_10M"],
@ -515,14 +515,14 @@ export const achievements: IMap<Achievement> = {
...achievementData["FIRST_HACKNET_SERVER"], ...achievementData["FIRST_HACKNET_SERVER"],
Icon: "HASHNET", Icon: "HASHNET",
Visible: () => hasAccessToSF(Player, 9), Visible: () => hasAccessToSF(Player, 9),
Condition: () => hasHacknetServers(Player) && Player.hacknetNodes.length > 0, Condition: () => hasHacknetServers() && Player.hacknetNodes.length > 0,
AdditionalUnlock: [achievementData.FIRST_HACKNET_NODE.ID], AdditionalUnlock: [achievementData.FIRST_HACKNET_NODE.ID],
}, },
ALL_HACKNET_SERVER: { ALL_HACKNET_SERVER: {
...achievementData["ALL_HACKNET_SERVER"], ...achievementData["ALL_HACKNET_SERVER"],
Icon: "HASHNETALL", Icon: "HASHNETALL",
Visible: () => hasAccessToSF(Player, 9), Visible: () => hasAccessToSF(Player, 9),
Condition: () => hasHacknetServers(Player) && Player.hacknetNodes.length === HacknetServerConstants.MaxServers, Condition: () => hasHacknetServers() && Player.hacknetNodes.length === HacknetServerConstants.MaxServers,
AdditionalUnlock: [achievementData["30_HACKNET_NODE"].ID], AdditionalUnlock: [achievementData["30_HACKNET_NODE"].ID],
}, },
MAX_HACKNET_SERVER: { MAX_HACKNET_SERVER: {
@ -530,7 +530,7 @@ export const achievements: IMap<Achievement> = {
Icon: "HASHNETALL", Icon: "HASHNETALL",
Visible: () => hasAccessToSF(Player, 9), Visible: () => hasAccessToSF(Player, 9),
Condition: (): boolean => { Condition: (): boolean => {
if (!hasHacknetServers(Player)) return false; if (!hasHacknetServers()) return false;
for (const h of Player.hacknetNodes) { for (const h of Player.hacknetNodes) {
if (typeof h !== "string") return false; if (typeof h !== "string") return false;
const hs = GetServer(h); const hs = GetServer(h);
@ -551,7 +551,7 @@ export const achievements: IMap<Achievement> = {
...achievementData["HACKNET_SERVER_1B"], ...achievementData["HACKNET_SERVER_1B"],
Icon: "HASHNETMONEY", Icon: "HASHNETMONEY",
Visible: () => hasAccessToSF(Player, 9), Visible: () => hasAccessToSF(Player, 9),
Condition: () => hasHacknetServers(Player) && Player.moneySourceB.hacknet >= 1e9, Condition: () => hasHacknetServers() && Player.moneySourceB.hacknet >= 1e9,
AdditionalUnlock: [achievementData.HACKNET_NODE_10M.ID], AdditionalUnlock: [achievementData.HACKNET_NODE_10M.ID],
}, },
MAX_CACHE: { MAX_CACHE: {
@ -559,7 +559,7 @@ export const achievements: IMap<Achievement> = {
Icon: "HASHNETCAP", Icon: "HASHNETCAP",
Visible: () => hasAccessToSF(Player, 9), Visible: () => hasAccessToSF(Player, 9),
Condition: () => Condition: () =>
hasHacknetServers(Player) && hasHacknetServers() &&
Player.hashManager.hashes === Player.hashManager.capacity && Player.hashManager.hashes === Player.hashManager.capacity &&
Player.hashManager.capacity > 0, Player.hashManager.capacity > 0,
}, },

@ -2,7 +2,7 @@ import React, { useState } from "react";
import { BBCabinetRoot } from "./BBCabinet"; import { BBCabinetRoot } from "./BBCabinet";
import Button from "@mui/material/Button"; import Button from "@mui/material/Button";
import { use } from "../../ui/Context"; import { Player } from "../../Player";
import { AlertEvents } from "../../ui/React/AlertManager"; import { AlertEvents } from "../../ui/React/AlertManager";
enum Page { enum Page {
@ -11,11 +11,10 @@ enum Page {
} }
export function ArcadeRoot(): React.ReactElement { export function ArcadeRoot(): React.ReactElement {
const player = use.Player();
const [page, setPage] = useState(Page.None); const [page, setPage] = useState(Page.None);
function mbBurner2000(): void { function mbBurner2000(): void {
if (player.sourceFileLvl(1) === 0) { if (Player.sourceFileLvl(1) === 0) {
AlertEvents.emit("This machine is broken."); AlertEvents.emit("This machine is broken.");
} else { } else {
setPage(Page.Megabyteburner2000); setPage(Page.Megabyteburner2000);

@ -1,6 +1,6 @@
import React, { useEffect } from "react"; import React, { useEffect } from "react";
import Typography from "@mui/material/Typography"; import Typography from "@mui/material/Typography";
import { use } from "../../ui/Context"; import { Player } from "../../Player";
import { Exploit } from "../../Exploits/Exploit"; import { Exploit } from "../../Exploits/Exploit";
const metaBB = "https://bitburner-official.github.io/bitburner-legacy/"; const metaBB = "https://bitburner-official.github.io/bitburner-legacy/";
@ -12,11 +12,10 @@ const style = {
}; };
export function BBCabinetRoot(): React.ReactElement { export function BBCabinetRoot(): React.ReactElement {
const player = use.Player();
useEffect(() => { useEffect(() => {
window.addEventListener("message", function (this: Window, ev: MessageEvent<boolean>) { window.addEventListener("message", function (this: Window, ev: MessageEvent<boolean>) {
if (ev.isTrusted && ev.origin == "https://bitburner-official.github.io" && ev.data) { if (ev.isTrusted && ev.origin == "https://bitburner-official.github.io" && ev.data) {
player.giveExploit(Exploit.TrueRecursion); Player.giveExploit(Exploit.TrueRecursion);
} }
}); });
}); });

@ -8,7 +8,7 @@ import { Money } from "../ui/React/Money";
import { Generic_fromJSON, Generic_toJSON, IReviverValue, Reviver } from "../utils/JSONReviver"; import { Generic_fromJSON, Generic_toJSON, IReviverValue, Reviver } from "../utils/JSONReviver";
import { FactionNames } from "../Faction/data/FactionNames"; import { FactionNames } from "../Faction/data/FactionNames";
import { IPlayer } from "../PersonObjects/IPlayer"; import { Player } from "../Player";
import { AugmentationNames } from "./data/AugmentationNames"; import { AugmentationNames } from "./data/AugmentationNames";
import { CONSTANTS } from "../Constants"; import { CONSTANTS } from "../Constants";
import { StaticAugmentations } from "./StaticAugmentations"; import { StaticAugmentations } from "./StaticAugmentations";
@ -531,26 +531,26 @@ export class Augmentation {
} }
} }
getCost(player: IPlayer): AugmentationCosts { getCost(): AugmentationCosts {
const augmentationReference = StaticAugmentations[this.name]; const augmentationReference = StaticAugmentations[this.name];
let moneyCost = augmentationReference.baseCost; let moneyCost = augmentationReference.baseCost;
let repCost = augmentationReference.baseRepRequirement; let repCost = augmentationReference.baseRepRequirement;
if (augmentationReference.name === AugmentationNames.NeuroFluxGovernor) { if (augmentationReference.name === AugmentationNames.NeuroFluxGovernor) {
let nextLevel = this.getLevel(player); let nextLevel = this.getLevel();
--nextLevel; --nextLevel;
const multiplier = Math.pow(CONSTANTS.NeuroFluxGovernorLevelMult, nextLevel); const multiplier = Math.pow(CONSTANTS.NeuroFluxGovernorLevelMult, nextLevel);
repCost = augmentationReference.baseRepRequirement * multiplier * BitNodeMultipliers.AugmentationRepCost; repCost = augmentationReference.baseRepRequirement * multiplier * BitNodeMultipliers.AugmentationRepCost;
moneyCost = augmentationReference.baseCost * multiplier * BitNodeMultipliers.AugmentationMoneyCost; moneyCost = augmentationReference.baseCost * multiplier * BitNodeMultipliers.AugmentationMoneyCost;
for (let i = 0; i < player.queuedAugmentations.length; ++i) { for (let i = 0; i < Player.queuedAugmentations.length; ++i) {
moneyCost *= getBaseAugmentationPriceMultiplier(); moneyCost *= getBaseAugmentationPriceMultiplier();
} }
} else if (augmentationReference.factions.includes(FactionNames.ShadowsOfAnarchy)) { } else if (augmentationReference.factions.includes(FactionNames.ShadowsOfAnarchy)) {
const soaAugmentationNames = initSoAAugmentations().map((augmentation) => augmentation.name); const soaAugmentationNames = initSoAAugmentations().map((augmentation) => augmentation.name);
const soaMultiplier = Math.pow( const soaMultiplier = Math.pow(
CONSTANTS.SoACostMult, CONSTANTS.SoACostMult,
soaAugmentationNames.filter((augmentationName) => player.hasAugmentation(augmentationName)).length, soaAugmentationNames.filter((augmentationName) => Player.hasAugmentation(augmentationName)).length,
); );
moneyCost = augmentationReference.baseCost * soaMultiplier; moneyCost = augmentationReference.baseCost * soaMultiplier;
if (soaAugmentationNames.find((augmentationName) => augmentationName === augmentationReference.name)) { if (soaAugmentationNames.find((augmentationName) => augmentationName === augmentationReference.name)) {
@ -566,19 +566,19 @@ export class Augmentation {
return { moneyCost, repCost }; return { moneyCost, repCost };
} }
getLevel(player: IPlayer): number { getLevel(): number {
// Get current Neuroflux level based on Player's augmentations // Get current Neuroflux level based on Player's augmentations
if (this.name === AugmentationNames.NeuroFluxGovernor) { if (this.name === AugmentationNames.NeuroFluxGovernor) {
let currLevel = 0; let currLevel = 0;
for (let i = 0; i < player.augmentations.length; ++i) { for (let i = 0; i < Player.augmentations.length; ++i) {
if (player.augmentations[i].name === AugmentationNames.NeuroFluxGovernor) { if (Player.augmentations[i].name === AugmentationNames.NeuroFluxGovernor) {
currLevel = player.augmentations[i].level; currLevel = Player.augmentations[i].level;
} }
} }
// Account for purchased but uninstalled Augmentations // Account for purchased but uninstalled Augmentations
for (let i = 0; i < player.queuedAugmentations.length; ++i) { for (let i = 0; i < Player.queuedAugmentations.length; ++i) {
if (player.queuedAugmentations[i].name == AugmentationNames.NeuroFluxGovernor) { if (Player.queuedAugmentations[i].name == AugmentationNames.NeuroFluxGovernor) {
++currLevel; ++currLevel;
} }
} }

@ -1,6 +1,6 @@
import { Augmentation } from "./Augmentation"; import { Augmentation } from "./Augmentation";
import { StaticAugmentations } from "./StaticAugmentations"; import { StaticAugmentations } from "./StaticAugmentations";
import { PlayerOwnedAugmentation, IPlayerOwnedAugmentation } from "./PlayerOwnedAugmentation"; import { PlayerOwnedAugmentation } from "./PlayerOwnedAugmentation";
import { AugmentationNames } from "./data/AugmentationNames"; import { AugmentationNames } from "./data/AugmentationNames";
import { CONSTANTS } from "../Constants"; import { CONSTANTS } from "../Constants";
@ -21,7 +21,7 @@ import {
initUnstableCircadianModulator, initUnstableCircadianModulator,
} from "./data/AugmentationCreator"; } from "./data/AugmentationCreator";
import { Router } from "../ui/GameRoot"; import { Router } from "../ui/GameRoot";
import { mergeAugmentation } from "../PersonObjects/Multipliers"; import { mergeMultipliers } from "../PersonObjects/Multipliers";
export function AddToStaticAugmentations(aug: Augmentation): void { export function AddToStaticAugmentations(aug: Augmentation): void {
const name = aug.name; const name = aug.name;
@ -71,11 +71,11 @@ function resetAugmentation(aug: Augmentation): void {
AddToStaticAugmentations(aug); AddToStaticAugmentations(aug);
} }
function applyAugmentation(aug: IPlayerOwnedAugmentation, reapply = false): void { function applyAugmentation(aug: PlayerOwnedAugmentation, reapply = false): void {
const staticAugmentation = StaticAugmentations[aug.name]; const staticAugmentation = StaticAugmentations[aug.name];
// Apply multipliers // Apply multipliers
Player.mults = mergeAugmentation(Player.mults, staticAugmentation.mults); Player.mults = mergeMultipliers(Player.mults, staticAugmentation.mults);
// Special logic for Congruity Implant // Special logic for Congruity Implant
if (aug.name === AugmentationNames.CongruityImplant && !reapply) { if (aug.name === AugmentationNames.CongruityImplant && !reapply) {
@ -126,15 +126,15 @@ function installAugmentations(force?: boolean): boolean {
if (ownedAug.name === AugmentationNames.NeuroFluxGovernor) { if (ownedAug.name === AugmentationNames.NeuroFluxGovernor) {
level = ` - ${ownedAug.level}`; level = ` - ${ownedAug.level}`;
} }
augmentationList += aug.name + level + "<br>"; augmentationList += aug.name + level + "\n";
} }
Player.queuedAugmentations = []; Player.queuedAugmentations = [];
if (!force) { if (!force) {
dialogBoxCreate( dialogBoxCreate(
"You slowly drift to sleep as scientists put you under in order " + "You slowly drift to sleep as scientists put you under in order " +
"to install the following Augmentations:<br>" + "to install the following Augmentations:\n" +
augmentationList + augmentationList +
"<br>You wake up in your home...you feel different...", "\nYou wake up in your home...you feel different...",
); );
} }
prestigeAugmentation(); prestigeAugmentation();
@ -146,8 +146,8 @@ function augmentationExists(name: string): boolean {
return StaticAugmentations.hasOwnProperty(name); return StaticAugmentations.hasOwnProperty(name);
} }
export function isRepeatableAug(aug: Augmentation): boolean { export function isRepeatableAug(aug: Augmentation | string): boolean {
const augName = aug instanceof Augmentation ? aug.name : aug; const augName = typeof aug === "string" ? aug : aug.name;
return augName === AugmentationNames.NeuroFluxGovernor; return augName === AugmentationNames.NeuroFluxGovernor;
} }

@ -6,8 +6,3 @@ export class PlayerOwnedAugmentation {
this.name = name; this.name = name;
} }
} }
export interface IPlayerOwnedAugmentation {
level: number;
name: string;
}

@ -6,7 +6,6 @@ import { WHRNG } from "../../Casino/RNG";
import React from "react"; import React from "react";
import { FactionNames } from "../../Faction/data/FactionNames"; import { FactionNames } from "../../Faction/data/FactionNames";
import { CONSTANTS } from "../../Constants"; import { CONSTANTS } from "../../Constants";
import { Faction } from "src/Faction/Faction";
interface CircadianBonus { interface CircadianBonus {
bonuses: { bonuses: {

@ -10,8 +10,6 @@ import { PurchasedAugmentations } from "./PurchasedAugmentations";
import { SourceFilesElement } from "./SourceFiles"; import { SourceFilesElement } from "./SourceFiles";
import { canGetBonus } from "../../ExportBonus"; import { canGetBonus } from "../../ExportBonus";
import { use } from "../../ui/Context";
import Typography from "@mui/material/Typography"; import Typography from "@mui/material/Typography";
import Button from "@mui/material/Button"; import Button from "@mui/material/Button";
import Tooltip from "@mui/material/Tooltip"; import Tooltip from "@mui/material/Tooltip";
@ -20,7 +18,7 @@ import Paper from "@mui/material/Paper";
import Container from "@mui/material/Container"; import Container from "@mui/material/Container";
import { Settings } from "../../Settings/Settings"; import { Settings } from "../../Settings/Settings";
import { ConfirmationModal } from "../../ui/React/ConfirmationModal"; import { ConfirmationModal } from "../../ui/React/ConfirmationModal";
import { IPlayer } from "../../PersonObjects/IPlayer"; import { Player } from "../../Player";
import { AugmentationNames } from "../data/AugmentationNames"; import { AugmentationNames } from "../data/AugmentationNames";
import { StaticAugmentations } from "../StaticAugmentations"; import { StaticAugmentations } from "../StaticAugmentations";
import { CONSTANTS } from "../../Constants"; import { CONSTANTS } from "../../Constants";
@ -29,12 +27,8 @@ import { Info } from "@mui/icons-material";
import { Link } from "@mui/material"; import { Link } from "@mui/material";
import { AlertEvents } from "../../ui/React/AlertManager"; import { AlertEvents } from "../../ui/React/AlertManager";
interface NFGDisplayProps { const NeuroFluxDisplay = (): React.ReactElement => {
player: IPlayer; const level = Player.augmentations.find((e) => e.name === AugmentationNames.NeuroFluxGovernor)?.level ?? 0;
}
const NeuroFluxDisplay = ({ player }: NFGDisplayProps): React.ReactElement => {
const level = player.augmentations.find((e) => e.name === AugmentationNames.NeuroFluxGovernor)?.level ?? 0;
const openBloodDonation = () => { const openBloodDonation = () => {
AlertEvents.emit( AlertEvents.emit(
@ -67,18 +61,14 @@ const NeuroFluxDisplay = ({ player }: NFGDisplayProps): React.ReactElement => {
); );
}; };
interface EntropyDisplayProps { const EntropyDisplay = (): React.ReactElement => {
player: IPlayer; return Player.entropy > 0 ? (
}
const EntropyDisplay = ({ player }: EntropyDisplayProps): React.ReactElement => {
return player.entropy > 0 ? (
<Paper sx={{ p: 1 }}> <Paper sx={{ p: 1 }}>
<Typography variant="h5" color={Settings.theme.error}> <Typography variant="h5" color={Settings.theme.error}>
Entropy Virus - Level {player.entropy} Entropy Virus - Level {Player.entropy}
</Typography> </Typography>
<Typography color={Settings.theme.error}> <Typography color={Settings.theme.error}>
<b>All multipliers decreased by:</b> {formatNumber((1 - CONSTANTS.EntropyEffect ** player.entropy) * 100, 3)}% <b>All multipliers decreased by:</b> {formatNumber((1 - CONSTANTS.EntropyEffect ** Player.entropy) * 100, 3)}%
(multiplicative) (multiplicative)
</Typography> </Typography>
</Paper> </Paper>
@ -94,7 +84,6 @@ interface IProps {
export function AugmentationsRoot(props: IProps): React.ReactElement { export function AugmentationsRoot(props: IProps): React.ReactElement {
const [installOpen, setInstallOpen] = useState(false); const [installOpen, setInstallOpen] = useState(false);
const player = use.Player();
const setRerender = useState(false)[1]; const setRerender = useState(false)[1];
function rerender(): void { function rerender(): void {
setRerender((o) => !o); setRerender((o) => !o);
@ -187,7 +176,7 @@ export function AugmentationsRoot(props: IProps): React.ReactElement {
<Box sx={{ display: "grid", width: "100%", gridTemplateColumns: "1fr 1fr" }}> <Box sx={{ display: "grid", width: "100%", gridTemplateColumns: "1fr 1fr" }}>
<Tooltip title={<Typography>'I never asked for this'</Typography>}> <Tooltip title={<Typography>'I never asked for this'</Typography>}>
<span> <span>
<Button sx={{ width: "100%" }} disabled={player.queuedAugmentations.length === 0} onClick={doInstall}> <Button sx={{ width: "100%" }} disabled={Player.queuedAugmentations.length === 0} onClick={doInstall}>
Install Augmentations Install Augmentations
</Button> </Button>
</span> </span>
@ -199,7 +188,7 @@ export function AugmentationsRoot(props: IProps): React.ReactElement {
</Tooltip> </Tooltip>
</Box> </Box>
</Paper> </Paper>
{player.queuedAugmentations.length > 0 ? ( {Player.queuedAugmentations.length > 0 ? (
<Box sx={{ display: "grid", gridTemplateColumns: "1fr 3fr" }}> <Box sx={{ display: "grid", gridTemplateColumns: "1fr 3fr" }}>
<PurchasedAugmentations /> <PurchasedAugmentations />
<PlayerMultipliers /> <PlayerMultipliers />
@ -216,14 +205,14 @@ export function AugmentationsRoot(props: IProps): React.ReactElement {
my: 1, my: 1,
display: "grid", display: "grid",
gridTemplateColumns: `repeat(${ gridTemplateColumns: `repeat(${
+!!((player.augmentations.find((e) => e.name === AugmentationNames.NeuroFluxGovernor)?.level ?? 0) > 0) + +!!((Player.augmentations.find((e) => e.name === AugmentationNames.NeuroFluxGovernor)?.level ?? 0) > 0) +
+!!(player.entropy > 0) +!!(Player.entropy > 0)
}, 1fr)`, }, 1fr)`,
gap: 1, gap: 1,
}} }}
> >
<NeuroFluxDisplay player={player} /> <NeuroFluxDisplay />
<EntropyDisplay player={player} /> <EntropyDisplay />
</Box> </Box>
<Box> <Box>

@ -12,14 +12,13 @@ import Tooltip from "@mui/material/Tooltip";
import React, { useState } from "react"; import React, { useState } from "react";
import { OwnedAugmentationsOrderSetting } from "../../Settings/SettingEnums"; import { OwnedAugmentationsOrderSetting } from "../../Settings/SettingEnums";
import { Settings } from "../../Settings/Settings"; import { Settings } from "../../Settings/Settings";
import { use } from "../../ui/Context"; import { Player } from "../../Player";
import { StaticAugmentations } from "../StaticAugmentations"; import { StaticAugmentations } from "../StaticAugmentations";
import { AugmentationNames } from "../data/AugmentationNames"; import { AugmentationNames } from "../data/AugmentationNames";
export function InstalledAugmentations(): React.ReactElement { export function InstalledAugmentations(): React.ReactElement {
const setRerender = useState(true)[1]; const setRerender = useState(true)[1];
const player = use.Player(); const sourceAugs = Player.augmentations.slice().filter((aug) => aug.name !== AugmentationNames.NeuroFluxGovernor);
const sourceAugs = player.augmentations.slice().filter((aug) => aug.name !== AugmentationNames.NeuroFluxGovernor);
const [selectedAug, setSelectedAug] = useState(sourceAugs[0]); const [selectedAug, setSelectedAug] = useState(sourceAugs[0]);

@ -4,7 +4,7 @@
import { DoubleArrow } from "@mui/icons-material"; import { DoubleArrow } from "@mui/icons-material";
import { List, ListItem, ListItemText, Paper, Typography } from "@mui/material"; import { List, ListItem, ListItemText, Paper, Typography } from "@mui/material";
import * as React from "react"; import * as React from "react";
import { Multipliers, defaultMultipliers, mergeAugmentation } from "../../PersonObjects/Multipliers"; import { Multipliers, defaultMultipliers, mergeMultipliers } from "../../PersonObjects/Multipliers";
import { BitNodeMultipliers } from "../../BitNode/BitNodeMultipliers"; import { BitNodeMultipliers } from "../../BitNode/BitNodeMultipliers";
import { Player } from "../../Player"; import { Player } from "../../Player";
import { Settings } from "../../Settings/Settings"; import { Settings } from "../../Settings/Settings";
@ -15,7 +15,7 @@ function calculateAugmentedStats(): Multipliers {
let augP: Multipliers = defaultMultipliers(); let augP: Multipliers = defaultMultipliers();
for (const aug of Player.queuedAugmentations) { for (const aug of Player.queuedAugmentations) {
const augObj = StaticAugmentations[aug.name]; const augObj = StaticAugmentations[aug.name];
augP = mergeAugmentation(augP, augObj.mults); augP = mergeMultipliers(augP, augObj.mults);
} }
return augP; return augP;
} }

@ -6,7 +6,7 @@ import { CheckBox, CheckBoxOutlineBlank, CheckCircle, NewReleases, Report } from
import { Box, Button, Container, Paper, Tooltip, Typography } from "@mui/material"; import { Box, Button, Container, Paper, Tooltip, Typography } from "@mui/material";
import React, { useState } from "react"; import React, { useState } from "react";
import { Faction } from "../../Faction/Faction"; import { Faction } from "../../Faction/Faction";
import { IPlayer } from "../../PersonObjects/IPlayer"; import { Player } from "../../Player";
import { Settings } from "../../Settings/Settings"; import { Settings } from "../../Settings/Settings";
import { numeralWrapper } from "../../ui/numeralFormat"; import { numeralWrapper } from "../../ui/numeralFormat";
import { Augmentation } from "../Augmentation"; import { Augmentation } from "../Augmentation";
@ -15,12 +15,11 @@ import { StaticAugmentations } from "../StaticAugmentations";
import { PurchaseAugmentationModal } from "./PurchaseAugmentationModal"; import { PurchaseAugmentationModal } from "./PurchaseAugmentationModal";
interface IPreReqsProps { interface IPreReqsProps {
player: IPlayer;
aug: Augmentation; aug: Augmentation;
} }
const PreReqs = (props: IPreReqsProps): React.ReactElement => { const PreReqs = (props: IPreReqsProps): React.ReactElement => {
const ownedPreReqs = props.aug.prereqs.filter((aug) => props.player.hasAugmentation(aug)); const ownedPreReqs = props.aug.prereqs.filter((aug) => Player.hasAugmentation(aug));
const hasPreReqs = props.aug.prereqs.length > 0 && ownedPreReqs.length === props.aug.prereqs.length; const hasPreReqs = props.aug.prereqs.length > 0 && ownedPreReqs.length === props.aug.prereqs.length;
return ( return (
@ -32,7 +31,7 @@ const PreReqs = (props: IPreReqsProps): React.ReactElement => {
</Typography> </Typography>
{props.aug.prereqs.map((preAug) => ( {props.aug.prereqs.map((preAug) => (
<Requirement <Requirement
fulfilled={props.player.hasAugmentation(preAug)} fulfilled={Player.hasAugmentation(preAug)}
value={preAug} value={preAug}
color={Settings.theme.money} color={Settings.theme.money}
key={preAug} key={preAug}
@ -68,7 +67,6 @@ const PreReqs = (props: IPreReqsProps): React.ReactElement => {
}; };
interface IExclusiveProps { interface IExclusiveProps {
player: IPlayer;
aug: Augmentation; aug: Augmentation;
} }
@ -85,18 +83,16 @@ const Exclusive = (props: IExclusiveProps): React.ReactElement => {
<li> <li>
<b>{props.aug.factions[0]}</b> faction <b>{props.aug.factions[0]}</b> faction
</li> </li>
{props.player.isAwareOfGang() && !props.aug.isSpecial && ( {Player.isAwareOfGang() && !props.aug.isSpecial && (
<li> <li>
Certain <b>gangs</b> Certain <b>gangs</b>
</li> </li>
)} )}
{props.player.canAccessGrafting() && {Player.canAccessGrafting() && !props.aug.isSpecial && props.aug.name !== AugmentationNames.TheRedPill && (
!props.aug.isSpecial && <li>
props.aug.name !== AugmentationNames.TheRedPill && ( <b>Grafting</b>
<li> </li>
<b>Grafting</b> )}
</li>
)}
</Typography> </Typography>
</ul> </ul>
</> </>
@ -130,10 +126,9 @@ const Requirement = (props: IReqProps): React.ReactElement => {
interface IPurchasableAugsProps { interface IPurchasableAugsProps {
augNames: string[]; augNames: string[];
ownedAugNames: string[]; ownedAugNames: string[];
player: IPlayer;
canPurchase: (player: IPlayer, aug: Augmentation) => boolean; canPurchase: (aug: Augmentation) => boolean;
purchaseAugmentation: (player: IPlayer, aug: Augmentation, showModal: (open: boolean) => void) => void; purchaseAugmentation: (aug: Augmentation, showModal: (open: boolean) => void) => void;
rep?: number; rep?: number;
sleeveAugs?: boolean; sleeveAugs?: boolean;
@ -167,7 +162,7 @@ export function PurchasableAugmentation(props: IPurchasableAugProps): React.Reac
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const aug = StaticAugmentations[props.augName]; const aug = StaticAugmentations[props.augName];
const augCosts = aug.getCost(props.parent.player); const augCosts = aug.getCost();
const cost = props.parent.sleeveAugs ? aug.baseCost : augCosts.moneyCost; const cost = props.parent.sleeveAugs ? aug.baseCost : augCosts.moneyCost;
const repCost = augCosts.repCost; const repCost = augCosts.repCost;
const info = typeof aug.info === "string" ? <span>{aug.info}</span> : aug.info; const info = typeof aug.info === "string" ? <span>{aug.info}</span> : aug.info;
@ -195,11 +190,11 @@ export function PurchasableAugmentation(props: IPurchasableAugProps): React.Reac
<Box sx={{ display: "flex", alignItems: "center" }}> <Box sx={{ display: "flex", alignItems: "center" }}>
<Button <Button
onClick={() => onClick={() =>
props.parent.purchaseAugmentation(props.parent.player, aug, (open): void => { props.parent.purchaseAugmentation(aug, (open): void => {
setOpen(open); setOpen(open);
}) })
} }
disabled={!props.parent.canPurchase(props.parent.player, aug) || props.owned} disabled={!props.parent.canPurchase(aug) || props.owned}
sx={{ width: "48px", height: "36px", float: "left", clear: "none", mr: 1 }} sx={{ width: "48px", height: "36px", float: "left", clear: "none", mr: 1 }}
> >
{props.owned ? "Owned" : "Buy"} {props.owned ? "Owned" : "Buy"}
@ -212,8 +207,7 @@ export function PurchasableAugmentation(props: IPurchasableAugProps): React.Reac
<> <>
<Typography variant="h5"> <Typography variant="h5">
{props.augName} {props.augName}
{props.augName === AugmentationNames.NeuroFluxGovernor && {props.augName === AugmentationNames.NeuroFluxGovernor && ` - Level ${aug.getLevel()}`}
` - Level ${aug.getLevel(props.parent.player)}`}
</Typography> </Typography>
<Typography>{description}</Typography> <Typography>{description}</Typography>
</> </>
@ -226,20 +220,16 @@ export function PurchasableAugmentation(props: IPurchasableAugProps): React.Reac
whiteSpace: "nowrap", whiteSpace: "nowrap",
overflow: "hidden", overflow: "hidden",
color: color:
props.owned || !props.parent.canPurchase(props.parent.player, aug) props.owned || !props.parent.canPurchase(aug) ? Settings.theme.disabled : Settings.theme.primary,
? Settings.theme.disabled
: Settings.theme.primary,
}} }}
> >
{aug.name} {aug.name}
{aug.name === AugmentationNames.NeuroFluxGovernor && ` - Level ${aug.getLevel(props.parent.player)}`} {aug.name === AugmentationNames.NeuroFluxGovernor && ` - Level ${aug.getLevel()}`}
</Typography> </Typography>
</Tooltip> </Tooltip>
{aug.factions.length === 1 && !props.parent.sleeveAugs && ( {aug.factions.length === 1 && !props.parent.sleeveAugs && <Exclusive aug={aug} />}
<Exclusive player={props.parent.player} aug={aug} /> {aug.prereqs.length > 0 && !props.parent.sleeveAugs && <PreReqs aug={aug} />}
)}
{aug.prereqs.length > 0 && !props.parent.sleeveAugs && <PreReqs player={props.parent.player} aug={aug} />}
</Box> </Box>
</Box> </Box>
</Box> </Box>
@ -247,7 +237,7 @@ export function PurchasableAugmentation(props: IPurchasableAugProps): React.Reac
{props.owned || ( {props.owned || (
<Box sx={{ display: "grid", alignItems: "center", gridTemplateColumns: "1fr 1fr" }}> <Box sx={{ display: "grid", alignItems: "center", gridTemplateColumns: "1fr 1fr" }}>
<Requirement <Requirement
fulfilled={cost === 0 || props.parent.player.money > cost} fulfilled={cost === 0 || Player.money > cost}
value={numeralWrapper.formatMoney(cost)} value={numeralWrapper.formatMoney(cost)}
color={Settings.theme.money} color={Settings.theme.money}
/> />

@ -6,7 +6,7 @@ import { purchaseAugmentation } from "../../Faction/FactionHelpers";
import { isRepeatableAug } from "../AugmentationHelpers"; import { isRepeatableAug } from "../AugmentationHelpers";
import { Money } from "../../ui/React/Money"; import { Money } from "../../ui/React/Money";
import { Modal } from "../../ui/React/Modal"; import { Modal } from "../../ui/React/Modal";
import { use } from "../../ui/Context"; import { Player } from "../../Player";
import Typography from "@mui/material/Typography"; import Typography from "@mui/material/Typography";
import Button from "@mui/material/Button"; import Button from "@mui/material/Button";
@ -18,14 +18,12 @@ interface IProps {
} }
export function PurchaseAugmentationModal(props: IProps): React.ReactElement { export function PurchaseAugmentationModal(props: IProps): React.ReactElement {
if (typeof props.aug === "undefined" || typeof props.faction === "undefined") { if (!props.aug || !props.faction) {
return <></>; return <></>;
} }
const player = use.Player();
function buy(): void { function buy(): void {
if (!isRepeatableAug(props.aug as Augmentation) && player.hasAugmentation(props.aug as Augmentation)) { if (!props.aug || (!isRepeatableAug(props.aug) && Player.hasAugmentation(props.aug.name))) {
return; return;
} }
@ -44,7 +42,7 @@ export function PurchaseAugmentationModal(props: IProps): React.ReactElement {
<br /> <br />
<br /> <br />
Would you like to purchase the {props.aug.name} Augmentation for&nbsp; Would you like to purchase the {props.aug.name} Augmentation for&nbsp;
<Money money={props.aug.getCost(player).moneyCost} />? <Money money={props.aug.getCost().moneyCost} />?
<br /> <br />
<br /> <br />
</Typography> </Typography>

@ -1,6 +1,6 @@
import React from "react"; import React from "react";
import { BitNodeMultipliers, IBitNodeMultipliers } from "./BitNodeMultipliers"; import { BitNodeMultipliers, IBitNodeMultipliers } from "./BitNodeMultipliers";
import { IPlayer } from "../PersonObjects/IPlayer"; import { Player } from "../Player";
import { IMap } from "../types"; import { IMap } from "../types";
import { FactionNames } from "../Faction/data/FactionNames"; import { FactionNames } from "../Faction/data/FactionNames";
import { CityName } from "../Locations/data/CityNames"; import { CityName } from "../Locations/data/CityNames";
@ -876,6 +876,6 @@ export function getBitNodeMultipliers(n: number, lvl: number): IBitNodeMultiplie
} }
} }
export function initBitNodeMultipliers(p: IPlayer): void { export function initBitNodeMultipliers(): void {
Object.assign(BitNodeMultipliers, getBitNodeMultipliers(p.bitNodeN, p.sourceFileLvl(p.bitNodeN))); Object.assign(BitNodeMultipliers, getBitNodeMultipliers(Player.bitNodeN, Player.sourceFileLvl(Player.bitNodeN)));
} }

@ -1,6 +1,6 @@
import React, { useState, useEffect } from "react"; import React, { useState, useEffect } from "react";
import { Modal } from "../../ui/React/Modal"; import { Modal } from "../../ui/React/Modal";
import { use } from "../../ui/Context"; import { Router } from "../../ui/GameRoot";
import { EventEmitter } from "../../utils/EventEmitter"; import { EventEmitter } from "../../utils/EventEmitter";
import Typography from "@mui/material/Typography"; import Typography from "@mui/material/Typography";
import Button from "@mui/material/Button"; import Button from "@mui/material/Button";
@ -8,10 +8,9 @@ import Button from "@mui/material/Button";
export const BitFlumeEvent = new EventEmitter<[]>(); export const BitFlumeEvent = new EventEmitter<[]>();
export function BitFlumeModal(): React.ReactElement { export function BitFlumeModal(): React.ReactElement {
const router = use.Router();
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
function flume(): void { function flume(): void {
router.toBitVerse(true, false); Router.toBitVerse(true, false);
setOpen(false); setOpen(false);
} }

@ -5,7 +5,7 @@ import { uniqueId } from "lodash";
import React from "react"; import React from "react";
import { SpecialServers } from "../../Server/data/SpecialServers"; import { SpecialServers } from "../../Server/data/SpecialServers";
import { Settings } from "../../Settings/Settings"; import { Settings } from "../../Settings/Settings";
import { use } from "../../ui/Context"; import { Player } from "../../Player";
import { StatsRow } from "../../ui/React/StatsRow"; import { StatsRow } from "../../ui/React/StatsRow";
import { defaultMultipliers, getBitNodeMultipliers } from "../BitNode"; import { defaultMultipliers, getBitNodeMultipliers } from "../BitNode";
import { IBitNodeMultipliers } from "../BitNodeMultipliers"; import { IBitNodeMultipliers } from "../BitNodeMultipliers";
@ -33,13 +33,12 @@ export function BitnodeMultiplierDescription({ n, level }: IProps): React.ReactE
} }
export const BitNodeMultipliersDisplay = ({ n, level }: IProps): React.ReactElement => { export const BitNodeMultipliersDisplay = ({ n, level }: IProps): React.ReactElement => {
const player = use.Player();
// If a level argument has been provided, use that as the multiplier level // If a level argument has been provided, use that as the multiplier level
// If not, then we have to assume that we want the next level up from the // If not, then we have to assume that we want the next level up from the
// current node's source file, so we get the min of that, the SF's max level, // current node's source file, so we get the min of that, the SF's max level,
// or if it's BN12, ∞ // or if it's BN12, ∞
const maxSfLevel = n === 12 ? Infinity : 3; const maxSfLevel = n === 12 ? Infinity : 3;
const mults = getBitNodeMultipliers(n, level ?? Math.min(player.sourceFileLvl(n) + 1, maxSfLevel)); const mults = getBitNodeMultipliers(n, level ?? Math.min(Player.sourceFileLvl(n) + 1, maxSfLevel));
return ( return (
<Box sx={{ columnCount: 2, columnGap: 1, mb: -2 }}> <Box sx={{ columnCount: 2, columnGap: 1, mb: -2 }}>
@ -277,8 +276,7 @@ function InfiltrationMults({ mults }: IMultsProps): React.ReactElement {
} }
function BladeburnerMults({ mults }: IMultsProps): React.ReactElement { function BladeburnerMults({ mults }: IMultsProps): React.ReactElement {
const player = use.Player(); if (!Player.canAccessBladeburner()) return <></>;
if (!player.canAccessBladeburner()) return <></>;
if (mults.BladeburnerRank === 0) { if (mults.BladeburnerRank === 0) {
const rows: IBNMultRows = { const rows: IBNMultRows = {
@ -297,8 +295,7 @@ function BladeburnerMults({ mults }: IMultsProps): React.ReactElement {
} }
function StanekMults({ mults }: IMultsProps): React.ReactElement { function StanekMults({ mults }: IMultsProps): React.ReactElement {
const player = use.Player(); if (!Player.canAccessCotMG()) return <></>;
if (!player.canAccessCotMG()) return <></>;
const extraSize = mults.StaneksGiftExtraSize.toFixed(3); const extraSize = mults.StaneksGiftExtraSize.toFixed(3);
const rows: IBNMultRows = { const rows: IBNMultRows = {
@ -313,8 +310,7 @@ function StanekMults({ mults }: IMultsProps): React.ReactElement {
} }
function GangMults({ mults }: IMultsProps): React.ReactElement { function GangMults({ mults }: IMultsProps): React.ReactElement {
const player = use.Player(); if (Player.bitNodeN !== 2 && Player.sourceFileLvl(2) <= 0) return <></>;
if (player.bitNodeN !== 2 && player.sourceFileLvl(2) <= 0) return <></>;
const rows: IBNMultRows = { const rows: IBNMultRows = {
GangSoftcap: { GangSoftcap: {
@ -328,8 +324,7 @@ function GangMults({ mults }: IMultsProps): React.ReactElement {
} }
function CorporationMults({ mults }: IMultsProps): React.ReactElement { function CorporationMults({ mults }: IMultsProps): React.ReactElement {
const player = use.Player(); if (!Player.canAccessCorporation()) return <></>;
if (!player.canAccessCorporation()) return <></>;
if (mults.CorporationSoftcap < 0.15) { if (mults.CorporationSoftcap < 0.15) {
const rows: IBNMultRows = { const rows: IBNMultRows = {

@ -1,10 +1,8 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { IRouter } from "../../ui/Router";
import { BitNodes } from "../BitNode"; import { BitNodes } from "../BitNode";
import { enterBitNode } from "../../RedPill";
import { PortalModal } from "./PortalModal"; import { PortalModal } from "./PortalModal";
import { CinematicText } from "../../ui/React/CinematicText"; import { CinematicText } from "../../ui/React/CinematicText";
import { use } from "../../ui/Context"; import { Player } from "../../Player";
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 IconButton from "@mui/material/IconButton"; import IconButton from "@mui/material/IconButton";
@ -46,7 +44,6 @@ interface IPortalProps {
level: number; level: number;
destroyedBitNode: number; destroyedBitNode: number;
flume: boolean; flume: boolean;
enter: (router: IRouter, flume: boolean, destroyedBitNode: number, newBitNode: number) => void;
} }
function BitNodePortal(props: IPortalProps): React.ReactElement { function BitNodePortal(props: IPortalProps): React.ReactElement {
const [portalOpen, setPortalOpen] = useState(false); const [portalOpen, setPortalOpen] = useState(false);
@ -105,7 +102,6 @@ function BitNodePortal(props: IPortalProps): React.ReactElement {
onClose={() => setPortalOpen(false)} onClose={() => setPortalOpen(false)}
n={props.n} n={props.n}
level={props.level} level={props.level}
enter={props.enter}
destroyedBitNode={props.destroyedBitNode} destroyedBitNode={props.destroyedBitNode}
flume={props.flume} flume={props.flume}
/> />
@ -118,13 +114,10 @@ function BitNodePortal(props: IPortalProps): React.ReactElement {
interface IProps { interface IProps {
flume: boolean; flume: boolean;
quick: boolean; quick: boolean;
enter: (router: IRouter, flume: boolean, destroyedBitNode: number, newBitNode: number) => void;
} }
export function BitverseRoot(props: IProps): React.ReactElement { export function BitverseRoot(props: IProps): React.ReactElement {
const player = use.Player(); const destroyed = Player.bitNodeN;
const enter = enterBitNode;
const destroyed = player.bitNodeN;
const [destroySequence, setDestroySequence] = useState(!props.quick); const [destroySequence, setDestroySequence] = useState(!props.quick);
if (destroySequence) { if (destroySequence) {
@ -158,7 +151,7 @@ export function BitverseRoot(props: IProps): React.ReactElement {
} }
const nextSourceFileLvl = (n: number): number => { const nextSourceFileLvl = (n: number): number => {
const lvl = player.sourceFileLvl(n); const lvl = Player.sourceFileLvl(n);
if (n !== destroyed) { if (n !== destroyed) {
return lvl; return lvl;
} }
@ -181,7 +174,6 @@ export function BitverseRoot(props: IProps): React.ReactElement {
key={node.number} key={node.number}
n={node.number} n={node.number}
level={nextSourceFileLvl(node.number)} level={nextSourceFileLvl(node.number)}
enter={enter}
flume={props.flume} flume={props.flume}
destroyedBitNode={destroyed} destroyedBitNode={destroyed}
/> />
@ -234,19 +226,19 @@ export function BitverseRoot(props: IProps): React.ReactElement {
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}>O | | | \| | O / _/ | / O | |/ | | | O</Typography> <Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}>O | | | \| | O / _/ | / O | |/ | | | O</Typography>
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}>| | | |O / | | O / | O O | | \ O| | | |</Typography> <Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}>| | | |O / | | O / | O O | | \ O| | | |</Typography>
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}>| | |/ \/ / __| | |/ \ | \ | |__ \ \/ \| | |</Typography> <Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}>| | |/ \/ / __| | |/ \ | \ | |__ \ \/ \| | |</Typography>
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> \| O | |_/ |\| \ <BitNodePortal n={13} level={n(13)} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> \__| \_| | O |/ </Typography> <Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> \| O | |_/ |\| \ <BitNodePortal n={13} level={n(13)} flume={props.flume} destroyedBitNode={destroyed} /> \__| \_| | O |/ </Typography>
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> | | |_/ | | \| / | \_| | | </Typography> <Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> | | |_/ | | \| / | \_| | | </Typography>
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> \| / \| | / / \ |/ </Typography> <Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> \| / \| | / / \ |/ </Typography>
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> | <BitNodePortal n={10} level={n(10)} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> | | / | <BitNodePortal n={11} level={n(11)} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> | </Typography> <Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> | <BitNodePortal n={10} level={n(10)} flume={props.flume} destroyedBitNode={destroyed} /> | | / | <BitNodePortal n={11} level={n(11)} flume={props.flume} destroyedBitNode={destroyed} /> | </Typography>
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> <BitNodePortal n={9} level={n(9)} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> | | | | | | | <BitNodePortal n={12} level={n(12)} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> </Typography> <Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> <BitNodePortal n={9} level={n(9)} flume={props.flume} destroyedBitNode={destroyed} /> | | | | | | | <BitNodePortal n={12} level={n(12)} flume={props.flume} destroyedBitNode={destroyed} /> </Typography>
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> | | | / / \ \ | | | </Typography> <Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> | | | / / \ \ | | | </Typography>
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> \| | / <BitNodePortal n={7} level={n(7)} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> / \ <BitNodePortal n={8} level={n(8)} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> \ | |/ </Typography> <Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> \| | / <BitNodePortal n={7} level={n(7)} flume={props.flume} destroyedBitNode={destroyed} /> / \ <BitNodePortal n={8} level={n(8)} flume={props.flume} destroyedBitNode={destroyed} /> \ | |/ </Typography>
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> \ | / / | | \ \ | / </Typography> <Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> \ | / / | | \ \ | / </Typography>
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> \ \JUMP <BitNodePortal n={5} level={n(5)} enter={enter} flume={props.flume} destroyedBitNode={destroyed} />3R | | | | | | R3<BitNodePortal n={6} level={n(6)} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> PMUJ/ / </Typography> <Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> \ \JUMP <BitNodePortal n={5} level={n(5)} flume={props.flume} destroyedBitNode={destroyed} />3R | | | | | | R3<BitNodePortal n={6} level={n(6)} flume={props.flume} destroyedBitNode={destroyed} /> PMUJ/ / </Typography>
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> \|| | | | | | | | | ||/ </Typography> <Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> \|| | | | | | | | | ||/ </Typography>
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> \| \_ | | | | | | _/ |/ </Typography> <Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> \| \_ | | | | | | _/ |/ </Typography>
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> \ \| / \ / \ |/ / </Typography> <Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> \ \| / \ / \ |/ / </Typography>
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> <BitNodePortal n={1} level={n(1)} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> |/ <BitNodePortal n={2} level={n(2)} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> | | <BitNodePortal n={3} level={n(3)} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> \| <BitNodePortal n={4} level={n(4)} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> </Typography> <Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> <BitNodePortal n={1} level={n(1)} flume={props.flume} destroyedBitNode={destroyed} /> |/ <BitNodePortal n={2} level={n(2)} flume={props.flume} destroyedBitNode={destroyed} /> | | <BitNodePortal n={3} level={n(3)} flume={props.flume} destroyedBitNode={destroyed} /> \| <BitNodePortal n={4} level={n(4)} flume={props.flume} destroyedBitNode={destroyed} /> </Typography>
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> | | | | | | | | </Typography> <Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> | | | | | | | | </Typography>
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> \JUMP3R|JUMP|3R| |R3|PMUJ|R3PMUJ/ </Typography> <Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> \JUMP3R|JUMP|3R| |R3|PMUJ|R3PMUJ/ </Typography>
<br /> <br />

@ -1,8 +1,7 @@
import React from "react"; import React from "react";
import { enterBitNode } from "../../RedPill";
import { BitNodes } from "../BitNode"; import { BitNodes } from "../BitNode";
import { IRouter } from "../../ui/Router";
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";
@ -15,11 +14,9 @@ interface IProps {
level: number; level: number;
destroyedBitNode: number; destroyedBitNode: number;
flume: boolean; flume: boolean;
enter: (router: IRouter, flume: boolean, destroyedBitNode: number, newBitNode: number) => void;
} }
export function PortalModal(props: IProps): React.ReactElement { export function PortalModal(props: IProps): React.ReactElement {
const router = use.Router();
const bitNodeKey = "BitNode" + props.n; const bitNodeKey = "BitNode" + props.n;
const bitNode = BitNodes[bitNodeKey]; const bitNode = BitNodes[bitNodeKey];
if (bitNode == null) throw new Error(`Could not find BitNode object for number: ${props.n}`); if (bitNode == null) throw new Error(`Could not find BitNode object for number: ${props.n}`);
@ -48,7 +45,7 @@ export function PortalModal(props: IProps): React.ReactElement {
aria-label={`enter-bitnode-${bitNode.number.toString()}`} aria-label={`enter-bitnode-${bitNode.number.toString()}`}
autoFocus={true} autoFocus={true}
onClick={() => { onClick={() => {
props.enter(router, props.flume, props.destroyedBitNode, props.n); enterBitNode(props.flume, props.destroyedBitNode, props.n);
props.onClose(); props.onClose();
}} }}
> >

@ -3,9 +3,12 @@ import { getRandomInt } from "../utils/helpers/getRandomInt";
import { addOffset } from "../utils/helpers/addOffset"; import { addOffset } from "../utils/helpers/addOffset";
import { Generic_fromJSON, Generic_toJSON, IReviverValue, Reviver } from "../utils/JSONReviver"; import { Generic_fromJSON, Generic_toJSON, IReviverValue, Reviver } from "../utils/JSONReviver";
import { BladeburnerConstants } from "./data/Constants"; import { BladeburnerConstants } from "./data/Constants";
import { IBladeburner } from "./IBladeburner"; import { Bladeburner } from "./Bladeburner";
import { IAction, ISuccessChanceParams } from "./IAction"; import { Person } from "../PersonObjects/Person";
import { IPerson } from "../PersonObjects/IPerson";
interface ISuccessChanceParams {
est: boolean;
}
class StatsMultiplier { class StatsMultiplier {
[key: string]: number; [key: string]: number;
@ -41,7 +44,7 @@ export interface IActionParams {
teamCount?: number; teamCount?: number;
} }
export class Action implements IAction { export class Action {
name = ""; name = "";
// Difficulty scales with level. See getDifficulty() method // Difficulty scales with level. See getDifficulty() method
@ -153,7 +156,7 @@ export class Action implements IAction {
* Tests for success. Should be called when an action has completed * Tests for success. Should be called when an action has completed
* @param inst {Bladeburner} - Bladeburner instance * @param inst {Bladeburner} - Bladeburner instance
*/ */
attempt(inst: IBladeburner, person: IPerson): boolean { attempt(inst: Bladeburner, person: Person): boolean {
return Math.random() < this.getSuccessChance(inst, person); return Math.random() < this.getSuccessChance(inst, person);
} }
@ -162,7 +165,7 @@ export class Action implements IAction {
return 1; return 1;
} }
getActionTime(inst: IBladeburner, person: IPerson): number { getActionTime(inst: Bladeburner, person: Person): number {
const difficulty = this.getDifficulty(); const difficulty = this.getDifficulty();
let baseTime = difficulty / BladeburnerConstants.DifficultyToTimeFactor; let baseTime = difficulty / BladeburnerConstants.DifficultyToTimeFactor;
const skillFac = inst.skillMultipliers.actionTime; // Always < 1 const skillFac = inst.skillMultipliers.actionTime; // Always < 1
@ -183,16 +186,16 @@ export class Action implements IAction {
// For actions that have teams. To be implemented by subtypes. // For actions that have teams. To be implemented by subtypes.
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
getTeamSuccessBonus(inst: IBladeburner): number { getTeamSuccessBonus(inst: Bladeburner): number {
return 1; return 1;
} }
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
getActionTypeSkillSuccessBonus(inst: IBladeburner): number { getActionTypeSkillSuccessBonus(inst: Bladeburner): number {
return 1; return 1;
} }
getChaosCompetencePenalty(inst: IBladeburner, params: ISuccessChanceParams): number { getChaosCompetencePenalty(inst: Bladeburner, params: ISuccessChanceParams): number {
const city = inst.getCurrentCity(); const city = inst.getCurrentCity();
if (params.est) { if (params.est) {
return Math.pow(city.popEst / BladeburnerConstants.PopulationThreshold, BladeburnerConstants.PopulationExponent); return Math.pow(city.popEst / BladeburnerConstants.PopulationThreshold, BladeburnerConstants.PopulationExponent);
@ -201,7 +204,7 @@ export class Action implements IAction {
} }
} }
getChaosDifficultyBonus(inst: IBladeburner /*, params: ISuccessChanceParams*/): number { getChaosDifficultyBonus(inst: Bladeburner /*, params: ISuccessChanceParams*/): number {
const city = inst.getCurrentCity(); const city = inst.getCurrentCity();
if (city.chaos > BladeburnerConstants.ChaosThreshold) { if (city.chaos > BladeburnerConstants.ChaosThreshold) {
const diff = 1 + (city.chaos - BladeburnerConstants.ChaosThreshold); const diff = 1 + (city.chaos - BladeburnerConstants.ChaosThreshold);
@ -212,7 +215,7 @@ export class Action implements IAction {
return 1; return 1;
} }
getEstSuccessChance(inst: IBladeburner, person: IPerson): [number, number] { getEstSuccessChance(inst: Bladeburner, person: Person): [number, number] {
function clamp(x: number): number { function clamp(x: number): number {
return Math.max(0, Math.min(x, 1)); return Math.max(0, Math.min(x, 1));
} }
@ -233,7 +236,7 @@ export class Action implements IAction {
* @params - options: * @params - options:
* est (bool): Get success chance estimate instead of real success chance * est (bool): Get success chance estimate instead of real success chance
*/ */
getSuccessChance(inst: IBladeburner, person: IPerson, params: ISuccessChanceParams = { est: false }): number { getSuccessChance(inst: Bladeburner, person: Person, params: ISuccessChanceParams = { est: false }): number {
if (inst == null) { if (inst == null) {
throw new Error("Invalid Bladeburner instance passed into Action.getSuccessChance"); throw new Error("Invalid Bladeburner instance passed into Action.getSuccessChance");
} }

@ -1,4 +1,3 @@
import { IActionIdentifier } from "./IActionIdentifier";
import { Generic_fromJSON, Generic_toJSON, IReviverValue, Reviver } from "../utils/JSONReviver"; import { Generic_fromJSON, Generic_toJSON, IReviverValue, Reviver } from "../utils/JSONReviver";
interface IParams { interface IParams {
@ -6,7 +5,7 @@ interface IParams {
type?: number; type?: number;
} }
export class ActionIdentifier implements IActionIdentifier { export class ActionIdentifier {
name = ""; name = "";
type = -1; type = -1;

@ -12,11 +12,11 @@ export class BlackOperation extends Operation {
return 1.5; return 1.5;
} }
getChaosCompetencePenalty(/*inst: IBladeburner, params: ISuccessChanceParams*/): number { getChaosCompetencePenalty(/*inst: Bladeburner, params: ISuccessChanceParams*/): number {
return 1; return 1;
} }
getChaosDifficultyBonus(/*inst: IBladeburner, params: ISuccessChanceParams*/): number { getChaosDifficultyBonus(/*inst: Bladeburner, params: ISuccessChanceParams*/): number {
return 1; return 1;
} }

@ -1,6 +1,4 @@
import { Reviver, Generic_toJSON, Generic_fromJSON, IReviverValue } from "../utils/JSONReviver"; import { Reviver, Generic_toJSON, Generic_fromJSON, IReviverValue } from "../utils/JSONReviver";
import { IBladeburner } from "./IBladeburner";
import { IActionIdentifier } from "./IActionIdentifier";
import { ActionIdentifier } from "./ActionIdentifier"; import { ActionIdentifier } from "./ActionIdentifier";
import { ActionTypes } from "./data/ActionTypes"; import { ActionTypes } from "./data/ActionTypes";
import { Growths } from "./data/Growths"; import { Growths } from "./data/Growths";
@ -13,11 +11,11 @@ import { formatNumber } from "../utils/StringHelperFunctions";
import { Skills } from "./Skills"; import { Skills } from "./Skills";
import { Skill } from "./Skill"; import { Skill } from "./Skill";
import { City } from "./City"; import { City } from "./City";
import { IAction } from "./IAction"; import { Action } from "./Action";
import { IPlayer } from "../PersonObjects/IPlayer"; import { Player } from "../Player";
import { createTaskTracker, ITaskTracker } from "../PersonObjects/ITaskTracker"; import { createTaskTracker, ITaskTracker } from "../PersonObjects/ITaskTracker";
import { IPerson } from "../PersonObjects/IPerson"; import { Person } from "../PersonObjects/Person";
import { IRouter } from "../ui/Router"; import { Router } from "../ui/GameRoot";
import { ConsoleHelpText } from "./data/Help"; import { ConsoleHelpText } from "./data/Help";
import { exceptionAlert } from "../utils/helpers/exceptionAlert"; import { exceptionAlert } from "../utils/helpers/exceptionAlert";
import { getRandomInt } from "../utils/helpers/getRandomInt"; import { getRandomInt } from "../utils/helpers/getRandomInt";
@ -25,7 +23,6 @@ import { BladeburnerConstants } from "./data/Constants";
import { numeralWrapper } from "../ui/numeralFormat"; import { numeralWrapper } from "../ui/numeralFormat";
import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers"; import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers";
import { addOffset } from "../utils/helpers/addOffset"; import { addOffset } from "../utils/helpers/addOffset";
import { Faction } from "../Faction/Faction";
import { Factions, factionExists } from "../Faction/Factions"; import { Factions, factionExists } from "../Faction/Factions";
import { calculateHospitalizationCost } from "../Hospital/Hospital"; import { calculateHospitalizationCost } from "../Hospital/Hospital";
import { dialogBoxCreate } from "../ui/React/DialogBox"; import { dialogBoxCreate } from "../ui/React/DialogBox";
@ -39,13 +36,13 @@ import { KEY } from "../utils/helpers/keyCodes";
import { isSleeveInfiltrateWork } from "../PersonObjects/Sleeve/Work/SleeveInfiltrateWork"; import { isSleeveInfiltrateWork } from "../PersonObjects/Sleeve/Work/SleeveInfiltrateWork";
import { isSleeveSupportWork } from "../PersonObjects/Sleeve/Work/SleeveSupportWork"; import { isSleeveSupportWork } from "../PersonObjects/Sleeve/Work/SleeveSupportWork";
interface BlackOpsAttempt { export interface BlackOpsAttempt {
error?: string; error?: string;
isAvailable?: boolean; isAvailable?: boolean;
action?: BlackOperation; action?: BlackOperation;
} }
export class Bladeburner implements IBladeburner { export class Bladeburner {
numHosp = 0; numHosp = 0;
moneyLost = 0; moneyLost = 0;
rank = 0; rank = 0;
@ -67,7 +64,7 @@ export class Bladeburner implements IBladeburner {
actionTimeCurrent = 0; actionTimeCurrent = 0;
actionTimeOverflow = 0; actionTimeOverflow = 0;
action: IActionIdentifier = new ActionIdentifier({ action: ActionIdentifier = new ActionIdentifier({
type: ActionTypes["Idle"], type: ActionTypes["Idle"],
}); });
@ -89,18 +86,18 @@ export class Bladeburner implements IBladeburner {
events: true, events: true,
}; };
automateEnabled = false; automateEnabled = false;
automateActionHigh: IActionIdentifier = new ActionIdentifier({ automateActionHigh: ActionIdentifier = new ActionIdentifier({
type: ActionTypes["Idle"], type: ActionTypes["Idle"],
}); });
automateThreshHigh = 0; automateThreshHigh = 0;
automateActionLow: IActionIdentifier = new ActionIdentifier({ automateActionLow: ActionIdentifier = new ActionIdentifier({
type: ActionTypes["Idle"], type: ActionTypes["Idle"],
}); });
automateThreshLow = 0; automateThreshLow = 0;
consoleHistory: string[] = []; consoleHistory: string[] = [];
consoleLogs: string[] = ["Bladeburner Console", "Type 'help' to see console commands"]; consoleLogs: string[] = ["Bladeburner Console", "Type 'help' to see console commands"];
constructor(player?: IPlayer) { constructor() {
for (let i = 0; i < BladeburnerConstants.CityNames.length; ++i) { for (let i = 0; i < BladeburnerConstants.CityNames.length; ++i) {
this.cities[BladeburnerConstants.CityNames[i]] = new City(BladeburnerConstants.CityNames[i]); this.cities[BladeburnerConstants.CityNames[i]] = new City(BladeburnerConstants.CityNames[i]);
} }
@ -108,16 +105,14 @@ export class Bladeburner implements IBladeburner {
this.updateSkillMultipliers(); // Calls resetSkillMultipliers() this.updateSkillMultipliers(); // Calls resetSkillMultipliers()
// Max Stamina is based on stats and Bladeburner-specific bonuses // Max Stamina is based on stats and Bladeburner-specific bonuses
if (player) this.calculateMaxStamina(player); this.calculateMaxStamina();
this.stamina = this.maxStamina; this.stamina = this.maxStamina;
this.create(); this.create();
} }
getCurrentCity(): City { getCurrentCity(): City {
const city = this.cities[this.city]; const city = this.cities[this.city];
if (!(city instanceof City)) { if (!city) throw new Error("Invalid city in Bladeburner.getCurrentCity()");
throw new Error("Bladeburner.getCurrentCity() did not properly return a City object");
}
return city; return city;
} }
@ -125,7 +120,7 @@ export class Bladeburner implements IBladeburner {
return Math.min(1, this.stamina / (0.5 * this.maxStamina)); return Math.min(1, this.stamina / (0.5 * this.maxStamina));
} }
canAttemptBlackOp(actionId: IActionIdentifier): BlackOpsAttempt { canAttemptBlackOp(actionId: ActionIdentifier): BlackOpsAttempt {
// Safety measure - don't repeat BlackOps that are already done // Safety measure - don't repeat BlackOps that are already done
if (this.blackops[actionId.name] != null) { if (this.blackops[actionId.name] != null) {
return { error: "Tried to start a Black Operation that had already been completed" }; return { error: "Tried to start a Black Operation that had already been completed" };
@ -162,7 +157,8 @@ export class Bladeburner implements IBladeburner {
return { isAvailable: true, action }; return { isAvailable: true, action };
} }
startAction(person: IPerson, actionId: IActionIdentifier): void { /** This function is only for the player. Sleeves use their own functions to perform blade work. */
startAction(actionId: ActionIdentifier): void {
if (actionId == null) return; if (actionId == null) return;
this.action = actionId; this.action = actionId;
this.actionTimeCurrent = 0; this.actionTimeCurrent = 0;
@ -179,7 +175,7 @@ export class Bladeburner implements IBladeburner {
if (action.count < 1) { if (action.count < 1) {
return this.resetAction(); return this.resetAction();
} }
this.actionTimeToComplete = action.getActionTime(this, person); this.actionTimeToComplete = action.getActionTime(this, Player);
} catch (e: unknown) { } catch (e: unknown) {
exceptionAlert(e); exceptionAlert(e);
} }
@ -196,7 +192,7 @@ export class Bladeburner implements IBladeburner {
if (actionId.name === "Raid" && this.getCurrentCity().comms === 0) { if (actionId.name === "Raid" && this.getCurrentCity().comms === 0) {
return this.resetAction(); return this.resetAction();
} }
this.actionTimeToComplete = action.getActionTime(this, person); this.actionTimeToComplete = action.getActionTime(this, Player);
} catch (e: unknown) { } catch (e: unknown) {
exceptionAlert(e); exceptionAlert(e);
} }
@ -214,14 +210,14 @@ export class Bladeburner implements IBladeburner {
if (testBlackOp.action === undefined) { if (testBlackOp.action === undefined) {
throw new Error("action should not be null"); throw new Error("action should not be null");
} }
this.actionTimeToComplete = testBlackOp.action.getActionTime(this, person); this.actionTimeToComplete = testBlackOp.action.getActionTime(this, Player);
} catch (e: unknown) { } catch (e: unknown) {
exceptionAlert(e); exceptionAlert(e);
} }
break; break;
} }
case ActionTypes["Recruitment"]: case ActionTypes["Recruitment"]:
this.actionTimeToComplete = this.getRecruitmentTime(person); this.actionTimeToComplete = this.getRecruitmentTime(Player);
break; break;
case ActionTypes["Training"]: case ActionTypes["Training"]:
case ActionTypes["FieldAnalysis"]: case ActionTypes["FieldAnalysis"]:
@ -234,7 +230,7 @@ export class Bladeburner implements IBladeburner {
this.actionTimeToComplete = 60; this.actionTimeToComplete = 60;
break; break;
default: default:
throw new Error("Invalid Action Type in startAction(Bladeburner,player, ): " + actionId.type); throw new Error("Invalid Action Type in bladeburner.startAction(): " + actionId.type);
} }
} }
@ -252,7 +248,7 @@ export class Bladeburner implements IBladeburner {
this.updateSkillMultipliers(); this.updateSkillMultipliers();
} }
executeConsoleCommands(player: IPlayer, commands: string): void { executeConsoleCommands(commands: string): void {
try { try {
// Console History // Console History
if (this.consoleHistory[this.consoleHistory.length - 1] != commands) { if (this.consoleHistory[this.consoleHistory.length - 1] != commands) {
@ -264,7 +260,7 @@ export class Bladeburner implements IBladeburner {
const arrayOfCommands = commands.split(";"); const arrayOfCommands = commands.split(";");
for (let i = 0; i < arrayOfCommands.length; ++i) { for (let i = 0; i < arrayOfCommands.length; ++i) {
this.executeConsoleCommand(player, arrayOfCommands[i]); this.executeConsoleCommand(arrayOfCommands[i]);
} }
} catch (e: unknown) { } catch (e: unknown) {
exceptionAlert(e); exceptionAlert(e);
@ -309,7 +305,7 @@ export class Bladeburner implements IBladeburner {
} }
// working on // working on
getActionIdFromTypeAndName(type = "", name = ""): IActionIdentifier | null { getActionIdFromTypeAndName(type = "", name = ""): ActionIdentifier | null {
if (type === "" || name === "") { if (type === "" || name === "") {
return null; return null;
} }
@ -394,7 +390,7 @@ export class Bladeburner implements IBladeburner {
return null; return null;
} }
executeStartConsoleCommand(player: IPlayer, args: string[]): void { executeStartConsoleCommand(args: string[]): void {
if (args.length !== 3) { if (args.length !== 3) {
this.postToConsole("Invalid usage of 'start' console command: start [type] [name]"); this.postToConsole("Invalid usage of 'start' console command: start [type] [name]");
this.postToConsole("Use 'help start' for more info"); this.postToConsole("Use 'help start' for more info");
@ -407,7 +403,7 @@ export class Bladeburner implements IBladeburner {
if (GeneralActions[name] != null) { if (GeneralActions[name] != null) {
this.action.type = ActionTypes[name]; this.action.type = ActionTypes[name];
this.action.name = name; this.action.name = name;
this.startAction(player, this.action); this.startAction(this.action);
} else { } else {
this.postToConsole("Invalid action name specified: " + args[2]); this.postToConsole("Invalid action name specified: " + args[2]);
} }
@ -417,7 +413,7 @@ export class Bladeburner implements IBladeburner {
if (this.contracts[name] != null) { if (this.contracts[name] != null) {
this.action.type = ActionTypes.Contract; this.action.type = ActionTypes.Contract;
this.action.name = name; this.action.name = name;
this.startAction(player, this.action); this.startAction(this.action);
} else { } else {
this.postToConsole("Invalid contract name specified: " + args[2]); this.postToConsole("Invalid contract name specified: " + args[2]);
} }
@ -429,7 +425,7 @@ export class Bladeburner implements IBladeburner {
if (this.operations[name] != null) { if (this.operations[name] != null) {
this.action.type = ActionTypes.Operation; this.action.type = ActionTypes.Operation;
this.action.name = name; this.action.name = name;
this.startAction(player, this.action); this.startAction(this.action);
} else { } else {
this.postToConsole("Invalid Operation name specified: " + args[2]); this.postToConsole("Invalid Operation name specified: " + args[2]);
} }
@ -441,7 +437,7 @@ export class Bladeburner implements IBladeburner {
if (BlackOperations[name] != null) { if (BlackOperations[name] != null) {
this.action.type = ActionTypes.BlackOperation; this.action.type = ActionTypes.BlackOperation;
this.action.name = name; this.action.name = name;
this.startAction(player, this.action); this.startAction(this.action);
} else { } else {
this.postToConsole("Invalid BlackOp name specified: " + args[2]); this.postToConsole("Invalid BlackOp name specified: " + args[2]);
} }
@ -542,7 +538,7 @@ export class Bladeburner implements IBladeburner {
case 3: { case 3: {
const skillName = args[2]; const skillName = args[2];
const skill = Skills[skillName]; const skill = Skills[skillName];
if (skill == null || !(skill instanceof Skill)) { if (!skill) {
this.postToConsole("Invalid skill name (Note that it is case-sensitive): " + skillName); this.postToConsole("Invalid skill name (Note that it is case-sensitive): " + skillName);
break; break;
} }
@ -684,10 +680,7 @@ export class Bladeburner implements IBladeburner {
".", ".",
); );
} else if (flag.toLowerCase().includes("en")) { } else if (flag.toLowerCase().includes("en")) {
if ( if (!this.automateActionLow || !this.automateActionHigh) {
!(this.automateActionLow instanceof ActionIdentifier) ||
!(this.automateActionHigh instanceof ActionIdentifier)
) {
return this.log("Failed to enable automation. Actions were not set"); return this.log("Failed to enable automation. Actions were not set");
} }
this.automateEnabled = true; this.automateEnabled = true;
@ -820,7 +813,7 @@ export class Bladeburner implements IBladeburner {
return args; return args;
} }
executeConsoleCommand(player: IPlayer, command: string): void { executeConsoleCommand(command: string): void {
command = command.trim(); command = command.trim();
command = command.replace(/\s\s+/g, " "); // Replace all whitespace w/ a single space command = command.replace(/\s\s+/g, " "); // Replace all whitespace w/ a single space
@ -845,7 +838,7 @@ export class Bladeburner implements IBladeburner {
this.executeSkillConsoleCommand(args); this.executeSkillConsoleCommand(args);
break; break;
case "start": case "start":
this.executeStartConsoleCommand(player, args); this.executeStartConsoleCommand(args);
break; break;
case "stop": case "stop":
this.resetAction(); this.resetAction();
@ -898,9 +891,7 @@ export class Bladeburner implements IBladeburner {
// Choose random source/destination city for events // Choose random source/destination city for events
const sourceCityName = BladeburnerConstants.CityNames[getRandomInt(0, 5)]; const sourceCityName = BladeburnerConstants.CityNames[getRandomInt(0, 5)];
const sourceCity = this.cities[sourceCityName]; const sourceCity = this.cities[sourceCityName];
if (!(sourceCity instanceof City)) { if (!sourceCity) throw new Error("Invalid sourceCity in Bladeburner.randomEvent()");
throw new Error("sourceCity was not a City object in Bladeburner.randomEvent()");
}
let destCityName = BladeburnerConstants.CityNames[getRandomInt(0, 5)]; let destCityName = BladeburnerConstants.CityNames[getRandomInt(0, 5)];
while (destCityName === sourceCityName) { while (destCityName === sourceCityName) {
@ -908,9 +899,7 @@ export class Bladeburner implements IBladeburner {
} }
const destCity = this.cities[destCityName]; const destCity = this.cities[destCityName];
if (!(sourceCity instanceof City) || !(destCity instanceof City)) { if (!sourceCity || !destCity) throw new Error("Invalid sourceCity or destCity in Bladeburner.randomEvent()");
throw new Error("sourceCity/destCity was not a City object in Bladeburner.randomEvent()");
}
if (chance <= 0.05) { if (chance <= 0.05) {
// New Synthoid Community, 5% // New Synthoid Community, 5%
@ -994,7 +983,7 @@ export class Bladeburner implements IBladeburner {
* @param action(Action obj) - Derived action class * @param action(Action obj) - Derived action class
* @param success(bool) - Whether action was successful * @param success(bool) - Whether action was successful
*/ */
getActionStats(action: IAction, person: IPerson, success: boolean): ITaskTracker { getActionStats(action: Action, person: Person, success: boolean): ITaskTracker {
const difficulty = action.getDifficulty(); const difficulty = action.getDifficulty();
/** /**
@ -1024,7 +1013,7 @@ export class Bladeburner implements IBladeburner {
}; };
} }
getDiplomacyEffectiveness(person: IPerson): number { getDiplomacyEffectiveness(person: Person): number {
// Returns a decimal by which the city's chaos level should be multiplied (e.g. 0.98) // Returns a decimal by which the city's chaos level should be multiplied (e.g. 0.98)
const CharismaLinearFactor = 1e3; const CharismaLinearFactor = 1e3;
const CharismaExponentialFactor = 0.045; const CharismaExponentialFactor = 0.045;
@ -1034,11 +1023,11 @@ export class Bladeburner implements IBladeburner {
return (100 - charismaEff) / 100; return (100 - charismaEff) / 100;
} }
getRecruitmentSuccessChance(person: IPerson): number { getRecruitmentSuccessChance(person: Person): number {
return Math.pow(person.skills.charisma, 0.45) / (this.teamSize - this.sleeveSize + 1); return Math.pow(person.skills.charisma, 0.45) / (this.teamSize - this.sleeveSize + 1);
} }
getRecruitmentTime(person: IPerson): number { getRecruitmentTime(person: Person): number {
const effCharisma = person.skills.charisma * this.skillMultipliers.effCha; const effCharisma = person.skills.charisma * this.skillMultipliers.effCha;
const charismaFactor = Math.pow(effCharisma, 0.81) + effCharisma / 90; const charismaFactor = Math.pow(effCharisma, 0.81) + effCharisma / 90;
return Math.max(10, Math.round(BladeburnerConstants.BaseRecruitmentTimeNeeded - charismaFactor)); return Math.max(10, Math.round(BladeburnerConstants.BaseRecruitmentTimeNeeded - charismaFactor));
@ -1105,7 +1094,7 @@ export class Bladeburner implements IBladeburner {
} }
} }
completeOperation(success: boolean, player: IPlayer): void { completeOperation(success: boolean): void {
if (this.action.type !== ActionTypes.Operation) { if (this.action.type !== ActionTypes.Operation) {
throw new Error("completeOperation() called even though current action is not an Operation"); throw new Error("completeOperation() called even though current action is not an Operation");
} }
@ -1126,7 +1115,7 @@ export class Bladeburner implements IBladeburner {
const losses = getRandomInt(0, max); const losses = getRandomInt(0, max);
this.teamSize -= losses; this.teamSize -= losses;
if (this.teamSize < this.sleeveSize) { if (this.teamSize < this.sleeveSize) {
const sup = player.sleeves.filter((x) => isSleeveSupportWork(x.currentWork)); const sup = Player.sleeves.filter((x) => isSleeveSupportWork(x.currentWork));
for (let i = 0; i > this.teamSize - this.sleeveSize; i--) { for (let i = 0; i > this.teamSize - this.sleeveSize; i--) {
const r = Math.floor(Math.random() * sup.length); const r = Math.floor(Math.random() * sup.length);
sup[r].takeDamage(sup[r].hp.max); sup[r].takeDamage(sup[r].hp.max);
@ -1201,7 +1190,7 @@ export class Bladeburner implements IBladeburner {
} }
} }
getActionObject(actionId: IActionIdentifier): IAction | null { getActionObject(actionId: ActionIdentifier): Action | null {
/** /**
* Given an ActionIdentifier object, returns the corresponding * Given an ActionIdentifier object, returns the corresponding
* GeneralAction, Contract, Operation, or BlackOperation object * GeneralAction, Contract, Operation, or BlackOperation object
@ -1231,7 +1220,7 @@ export class Bladeburner implements IBladeburner {
} }
} }
completeContract(success: boolean, actionIdent: IActionIdentifier): void { completeContract(success: boolean, actionIdent: ActionIdentifier): void {
if (actionIdent.type !== ActionTypes.Contract) { if (actionIdent.type !== ActionTypes.Contract) {
throw new Error("completeContract() called even though current action is not a Contract"); throw new Error("completeContract() called even though current action is not a Contract");
} }
@ -1256,7 +1245,7 @@ export class Bladeburner implements IBladeburner {
} }
} }
completeAction(player: IPlayer, person: IPerson, actionIdent: IActionIdentifier, isPlayer = true): ITaskTracker { completeAction(person: Person, actionIdent: ActionIdentifier, isPlayer = true): ITaskTracker {
let retValue = createTaskTracker(); let retValue = createTaskTracker();
switch (actionIdent.type) { switch (actionIdent.type) {
case ActionTypes["Contract"]: case ActionTypes["Contract"]:
@ -1304,24 +1293,16 @@ export class Bladeburner implements IBladeburner {
this.changeRank(person, gain); this.changeRank(person, gain);
if (isOperation && this.logging.ops) { if (isOperation && this.logging.ops) {
this.log( this.log(
`${person.whoAmI()}: ` + `${person.whoAmI()}: ${action.name} successfully completed! Gained ${formatNumber(gain, 3)} rank`,
action.name +
" successfully completed! Gained " +
formatNumber(gain, 3) +
" rank",
); );
} else if (!isOperation && this.logging.contracts) { } else if (!isOperation && this.logging.contracts) {
this.log( this.log(
`${person.whoAmI()}: ` + `${person.whoAmI()}: ${action.name} contract successfully completed! Gained ` +
action.name + `${formatNumber(gain, 3)} rank and ${numeralWrapper.formatMoney(moneyGain)}`,
" contract successfully completed! Gained " +
formatNumber(gain, 3) +
" rank and " +
numeralWrapper.formatMoney(moneyGain),
); );
} }
} }
isOperation ? this.completeOperation(true, player) : this.completeContract(true, actionIdent); isOperation ? this.completeOperation(true) : this.completeContract(true, actionIdent);
} else { } else {
retValue = this.getActionStats(action, person, false); retValue = this.getActionStats(action, person, false);
++action.failures; ++action.failures;
@ -1335,7 +1316,7 @@ export class Bladeburner implements IBladeburner {
damage = action.hpLoss * difficultyMultiplier; damage = action.hpLoss * difficultyMultiplier;
damage = Math.ceil(addOffset(damage, 10)); damage = Math.ceil(addOffset(damage, 10));
this.hpLost += damage; this.hpLost += damage;
const cost = calculateHospitalizationCost(player, damage); const cost = calculateHospitalizationCost(damage);
if (person.takeDamage(damage)) { if (person.takeDamage(damage)) {
++this.numHosp; ++this.numHosp;
this.moneyLost += cost; this.moneyLost += cost;
@ -1353,7 +1334,7 @@ export class Bladeburner implements IBladeburner {
} else if (!isOperation && this.logging.contracts) { } else if (!isOperation && this.logging.contracts) {
this.log(`${person.whoAmI()}: ` + action.name + " contract failed! " + logLossText); this.log(`${person.whoAmI()}: ` + action.name + " contract failed! " + logLossText);
} }
isOperation ? this.completeOperation(false, player) : this.completeContract(false, actionIdent); isOperation ? this.completeOperation(false) : this.completeContract(false, actionIdent);
} }
if (action.autoLevel) { if (action.autoLevel) {
action.level = action.maxLevel; action.level = action.maxLevel;
@ -1412,7 +1393,7 @@ export class Bladeburner implements IBladeburner {
if (action.hpLoss) { if (action.hpLoss) {
damage = action.hpLoss * difficultyMultiplier; damage = action.hpLoss * difficultyMultiplier;
damage = Math.ceil(addOffset(damage, 10)); damage = Math.ceil(addOffset(damage, 10));
const cost = calculateHospitalizationCost(player, damage); const cost = calculateHospitalizationCost(damage);
if (person.takeDamage(damage)) { if (person.takeDamage(damage)) {
++this.numHosp; ++this.numHosp;
this.moneyLost += cost; this.moneyLost += cost;
@ -1440,7 +1421,7 @@ export class Bladeburner implements IBladeburner {
const losses = getRandomInt(1, teamLossMax); const losses = getRandomInt(1, teamLossMax);
this.teamSize -= losses; this.teamSize -= losses;
if (this.teamSize < this.sleeveSize) { if (this.teamSize < this.sleeveSize) {
const sup = player.sleeves.filter((x) => isSleeveSupportWork(x.currentWork)); const sup = Player.sleeves.filter((x) => isSleeveSupportWork(x.currentWork));
for (let i = 0; i > this.teamSize - this.sleeveSize; i--) { for (let i = 0; i > this.teamSize - this.sleeveSize; i--) {
const r = Math.floor(Math.random() * sup.length); const r = Math.floor(Math.random() * sup.length);
sup[r].takeDamage(sup[r].hp.max); sup[r].takeDamage(sup[r].hp.max);
@ -1603,8 +1584,8 @@ export class Bladeburner implements IBladeburner {
return retValue; return retValue;
} }
infiltrateSynthoidCommunities(p: IPlayer): void { infiltrateSynthoidCommunities(): void {
const infilSleeves = p.sleeves.filter((s) => isSleeveInfiltrateWork(s.currentWork)).length; const infilSleeves = Player.sleeves.filter((s) => isSleeveInfiltrateWork(s.currentWork)).length;
const amt = Math.pow(infilSleeves, -0.5) / 2; const amt = Math.pow(infilSleeves, -0.5) / 2;
for (const contract of Object.keys(this.contracts)) { for (const contract of Object.keys(this.contracts)) {
this.contracts[contract].count += amt; this.contracts[contract].count += amt;
@ -1617,7 +1598,7 @@ export class Bladeburner implements IBladeburner {
} }
} }
changeRank(person: IPerson, change: number): void { changeRank(person: Person, change: number): void {
if (isNaN(change)) { if (isNaN(change)) {
throw new Error("NaN passed into Bladeburner.changeRank()"); throw new Error("NaN passed into Bladeburner.changeRank()");
} }
@ -1630,7 +1611,7 @@ export class Bladeburner implements IBladeburner {
const bladeburnersFactionName = FactionNames.Bladeburners; const bladeburnersFactionName = FactionNames.Bladeburners;
if (factionExists(bladeburnersFactionName)) { if (factionExists(bladeburnersFactionName)) {
const bladeburnerFac = Factions[bladeburnersFactionName]; const bladeburnerFac = Factions[bladeburnersFactionName];
if (!(bladeburnerFac instanceof Faction)) { if (!bladeburnerFac) {
throw new Error( throw new Error(
`Could not properly get ${FactionNames.Bladeburners} Faction object in ${FactionNames.Bladeburners} UI Overview Faction button`, `Could not properly get ${FactionNames.Bladeburners} Faction object in ${FactionNames.Bladeburners} UI Overview Faction button`,
); );
@ -1654,12 +1635,12 @@ export class Bladeburner implements IBladeburner {
} }
} }
processAction(router: IRouter, player: IPlayer, seconds: number): void { processAction(seconds: number): void {
if (this.action.type === ActionTypes["Idle"]) return; if (this.action.type === ActionTypes["Idle"]) return;
if (this.actionTimeToComplete <= 0) { if (this.actionTimeToComplete <= 0) {
throw new Error(`Invalid actionTimeToComplete value: ${this.actionTimeToComplete}, type; ${this.action.type}`); throw new Error(`Invalid actionTimeToComplete value: ${this.actionTimeToComplete}, type; ${this.action.type}`);
} }
if (!(this.action instanceof ActionIdentifier)) { if (!this.action) {
throw new Error("Bladeburner.action is not an ActionIdentifier Object"); throw new Error("Bladeburner.action is not an ActionIdentifier Object");
} }
@ -1670,31 +1651,31 @@ export class Bladeburner implements IBladeburner {
if (this.actionTimeCurrent >= this.actionTimeToComplete) { if (this.actionTimeCurrent >= this.actionTimeToComplete) {
this.actionTimeOverflow = this.actionTimeCurrent - this.actionTimeToComplete; this.actionTimeOverflow = this.actionTimeCurrent - this.actionTimeToComplete;
const action = this.getActionObject(this.action); const action = this.getActionObject(this.action);
const retValue = this.completeAction(player, player, this.action); const retValue = this.completeAction(Player, this.action);
player.gainMoney(retValue.money, "bladeburner"); Player.gainMoney(retValue.money, "bladeburner");
player.gainStats(retValue); Player.gainStats(retValue);
// Operation Daedalus // Operation Daedalus
if (action == null) { if (action == null) {
throw new Error("Failed to get BlackOperation Object for: " + this.action.name); throw new Error("Failed to get BlackOperation Object for: " + this.action.name);
} else if (this.action.type != ActionTypes["BlackOperation"] && this.action.type != ActionTypes["BlackOp"]) { } else if (this.action.type != ActionTypes["BlackOperation"] && this.action.type != ActionTypes["BlackOp"]) {
this.startAction(player, this.action); // Repeat action this.startAction(this.action); // Repeat action
} }
} }
} }
calculateStaminaGainPerSecond(player: IPlayer): number { calculateStaminaGainPerSecond(): number {
const effAgility = player.skills.agility * this.skillMultipliers.effAgi; const effAgility = Player.skills.agility * this.skillMultipliers.effAgi;
const maxStaminaBonus = this.maxStamina / BladeburnerConstants.MaxStaminaToGainFactor; const maxStaminaBonus = this.maxStamina / BladeburnerConstants.MaxStaminaToGainFactor;
const gain = (BladeburnerConstants.StaminaGainPerSecond + maxStaminaBonus) * Math.pow(effAgility, 0.17); const gain = (BladeburnerConstants.StaminaGainPerSecond + maxStaminaBonus) * Math.pow(effAgility, 0.17);
return gain * (this.skillMultipliers.stamina * player.mults.bladeburner_stamina_gain); return gain * (this.skillMultipliers.stamina * Player.mults.bladeburner_stamina_gain);
} }
calculateMaxStamina(player: IPlayer): void { calculateMaxStamina(): void {
const effAgility = player.skills.agility * this.skillMultipliers.effAgi; const effAgility = Player.skills.agility * this.skillMultipliers.effAgi;
const maxStamina = const maxStamina =
(Math.pow(effAgility, 0.8) + this.staminaBonus) * (Math.pow(effAgility, 0.8) + this.staminaBonus) *
this.skillMultipliers.stamina * this.skillMultipliers.stamina *
player.mults.bladeburner_max_stamina; Player.mults.bladeburner_max_stamina;
if (this.maxStamina !== maxStamina) { if (this.maxStamina !== maxStamina) {
const oldMax = this.maxStamina; const oldMax = this.maxStamina;
this.maxStamina = maxStamina; this.maxStamina = maxStamina;
@ -1974,12 +1955,12 @@ export class Bladeburner implements IBladeburner {
}); });
} }
process(router: IRouter, player: IPlayer): void { process(): void {
// Edge race condition when the engine checks the processing counters and attempts to route before the router is initialized. // Edge race condition when the engine checks the processing counters and attempts to route before the router is initialized.
if (!router.isInitialized) return; if (!Router.isInitialized) return;
// If the Player starts doing some other actions, set action to idle and alert // If the Player starts doing some other actions, set action to idle and alert
if (!player.hasAugmentation(AugmentationNames.BladesSimulacrum, true) && player.currentWork) { if (!Player.hasAugmentation(AugmentationNames.BladesSimulacrum, true) && Player.currentWork) {
if (this.action.type !== ActionTypes["Idle"]) { if (this.action.type !== ActionTypes["Idle"]) {
let msg = "Your Bladeburner action was cancelled because you started doing something else."; let msg = "Your Bladeburner action was cancelled because you started doing something else.";
if (this.automateEnabled) { if (this.automateEnabled) {
@ -2006,8 +1987,8 @@ export class Bladeburner implements IBladeburner {
this.storedCycles -= seconds * BladeburnerConstants.CyclesPerSecond; this.storedCycles -= seconds * BladeburnerConstants.CyclesPerSecond;
// Stamina // Stamina
this.calculateMaxStamina(player); this.calculateMaxStamina();
this.stamina += this.calculateStaminaGainPerSecond(player) * seconds; this.stamina += this.calculateStaminaGainPerSecond() * seconds;
this.stamina = Math.min(this.maxStamina, this.stamina); this.stamina = Math.min(this.maxStamina, this.stamina);
// Count increase for contracts/operations // Count increase for contracts/operations
@ -2027,9 +2008,7 @@ export class Bladeburner implements IBladeburner {
// Chaos goes down very slowly // Chaos goes down very slowly
for (const cityName of BladeburnerConstants.CityNames) { for (const cityName of BladeburnerConstants.CityNames) {
const city = this.cities[cityName]; const city = this.cities[cityName];
if (!(city instanceof City)) { if (!city) throw new Error("Invalid city when processing passive chaos reduction in Bladeburner.process");
throw new Error("Invalid City object when processing passive chaos reduction in Bladeburner.process");
}
city.chaos -= 0.0001 * seconds; city.chaos -= 0.0001 * seconds;
city.chaos = Math.max(0, city.chaos); city.chaos = Math.max(0, city.chaos);
} }
@ -2042,7 +2021,7 @@ export class Bladeburner implements IBladeburner {
this.randomEventCounter += getRandomInt(240, 600); this.randomEventCounter += getRandomInt(240, 600);
} }
this.processAction(router, player, seconds); this.processAction(seconds);
// Automation // Automation
if (this.automateEnabled) { if (this.automateEnabled) {
@ -2053,7 +2032,7 @@ export class Bladeburner implements IBladeburner {
type: this.automateActionLow.type, type: this.automateActionLow.type,
name: this.automateActionLow.name, name: this.automateActionLow.name,
}); });
this.startAction(player, this.action); this.startAction(this.action);
} }
} else if (this.stamina >= this.automateThreshHigh) { } else if (this.stamina >= this.automateThreshHigh) {
if (this.action.name !== this.automateActionHigh.name || this.action.type !== this.automateActionHigh.type) { if (this.action.name !== this.automateActionHigh.name || this.action.type !== this.automateActionHigh.type) {
@ -2061,14 +2040,14 @@ export class Bladeburner implements IBladeburner {
type: this.automateActionHigh.type, type: this.automateActionHigh.type,
name: this.automateActionHigh.name, name: this.automateActionHigh.name,
}); });
this.startAction(player, this.action); this.startAction(this.action);
} }
} }
} }
} }
} }
getTypeAndNameFromActionId(actionId: IActionIdentifier): { getTypeAndNameFromActionId(actionId: ActionIdentifier): {
type: string; type: string;
name: string; name: string;
} { } {
@ -2121,7 +2100,7 @@ export class Bladeburner implements IBladeburner {
return Object.keys(Skills); return Object.keys(Skills);
} }
startActionNetscriptFn(player: IPlayer, type: string, name: string, workerScript: WorkerScript): boolean { startActionNetscriptFn(type: string, name: string, workerScript: WorkerScript): boolean {
const errorLogText = `Invalid action: type='${type}' name='${name}'`; const errorLogText = `Invalid action: type='${type}' name='${name}'`;
const actionId = this.getActionIdFromTypeAndName(type, name); const actionId = this.getActionIdFromTypeAndName(type, name);
if (actionId == null) { if (actionId == null) {
@ -2139,7 +2118,7 @@ export class Bladeburner implements IBladeburner {
} }
try { try {
this.startAction(player, actionId); this.startAction(actionId);
workerScript.log( workerScript.log(
"bladeburner.startAction", "bladeburner.startAction",
() => `Starting bladeburner action with type '${type}' and name '${name}'`, () => `Starting bladeburner action with type '${type}' and name '${name}'`,
@ -2153,7 +2132,7 @@ export class Bladeburner implements IBladeburner {
} }
} }
getActionTimeNetscriptFn(person: IPerson, type: string, name: string): number | string { getActionTimeNetscriptFn(person: Person, type: string, name: string): number | string {
const actionId = this.getActionIdFromTypeAndName(type, name); const actionId = this.getActionIdFromTypeAndName(type, name);
if (actionId == null) { if (actionId == null) {
return "bladeburner.getActionTime"; return "bladeburner.getActionTime";
@ -2184,7 +2163,7 @@ export class Bladeburner implements IBladeburner {
} }
} }
getActionEstimatedSuccessChanceNetscriptFn(person: IPerson, type: string, name: string): [number, number] | string { getActionEstimatedSuccessChanceNetscriptFn(person: Person, type: string, name: string): [number, number] | string {
const actionId = this.getActionIdFromTypeAndName(type, name); const actionId = this.getActionIdFromTypeAndName(type, name);
if (actionId == null) { if (actionId == null) {
return "bladeburner.getActionEstimatedSuccessChance"; return "bladeburner.getActionEstimatedSuccessChance";

@ -1,4 +1,4 @@
import { IBladeburner } from "./IBladeburner"; import { Bladeburner } from "./Bladeburner";
import { Action, IActionParams } from "./Action"; import { Action, IActionParams } from "./Action";
import { Generic_fromJSON, Generic_toJSON, IReviverValue, Reviver } from "../utils/JSONReviver"; import { Generic_fromJSON, Generic_toJSON, IReviverValue, Reviver } from "../utils/JSONReviver";
@ -7,7 +7,7 @@ export class Contract extends Action {
super(params); super(params);
} }
getActionTypeSkillSuccessBonus(inst: IBladeburner): number { getActionTypeSkillSuccessBonus(inst: Bladeburner): number {
return inst.skillMultipliers.successChanceContract; return inst.skillMultipliers.successChanceContract;
} }

@ -1,72 +0,0 @@
import { IReviverValue } from "../utils/JSONReviver";
import { IPerson } from "../PersonObjects/IPerson";
import { IBladeburner } from "./IBladeburner";
interface IStatsMultiplier {
[key: string]: number;
hack: number;
str: number;
def: number;
dex: number;
agi: number;
cha: number;
int: number;
}
export interface ISuccessChanceParams {
est: boolean;
}
export interface IAction {
name: string;
// Difficulty scales with level. See getDifficulty() method
level: number;
maxLevel: number;
autoLevel: boolean;
baseDifficulty: number;
difficultyFac: number;
// Rank increase/decrease is affected by this exponent
rewardFac: number;
successes: number;
failures: number;
// All of these scale with level/difficulty
rankGain: number;
rankLoss: number;
hpLoss: number;
hpLost: number;
// Action Category. Current categories are stealth and kill
isStealth: boolean;
isKill: boolean;
/**
* Number of this contract remaining, and its growth rate
* Growth rate is an integer and the count will increase by that integer every "cycle"
*/
count: number;
// Weighting of each stat in determining action success rate
weights: IStatsMultiplier;
// Diminishing returns of stats (stat ^ decay where 0 <= decay <= 1)
decays: IStatsMultiplier;
teamCount: number;
getDifficulty(): number;
attempt(inst: IBladeburner, person: IPerson): boolean;
getActionTimePenalty(): number;
getActionTime(inst: IBladeburner, person: IPerson): number;
getTeamSuccessBonus(inst: IBladeburner): number;
getActionTypeSkillSuccessBonus(inst: IBladeburner): number;
getChaosCompetencePenalty(inst: IBladeburner, params: ISuccessChanceParams): number;
getChaosDifficultyBonus(inst: IBladeburner): number;
getEstSuccessChance(inst: IBladeburner, person: IPerson): [number, number];
getSuccessChance(inst: IBladeburner, person: IPerson, params: ISuccessChanceParams): number;
getSuccessesNeededForNextLevel(baseSuccessesPerLevel: number): number;
setMaxLevel(baseSuccessesPerLevel: number): void;
toJSON(): IReviverValue;
}

@ -1,4 +0,0 @@
export interface IActionIdentifier {
name: string;
type: number;
}

@ -1,121 +0,0 @@
import { IActionIdentifier } from "./IActionIdentifier";
import { City } from "./City";
import { Skill } from "./Skill";
import { IAction } from "./IAction";
import { IPlayer } from "../PersonObjects/IPlayer";
import { IPerson } from "../PersonObjects/IPerson";
import { ITaskTracker } from "../PersonObjects/ITaskTracker";
import { IRouter } from "../ui/Router";
import { WorkerScript } from "../Netscript/WorkerScript";
import { Contract } from "./Contract";
import { Operation } from "./Operation";
export interface IBladeburner {
numHosp: number;
moneyLost: number;
rank: number;
maxRank: number;
skillPoints: number;
totalSkillPoints: number;
teamSize: number;
teamLost: number;
hpLost: number;
storedCycles: number;
randomEventCounter: number;
actionTimeToComplete: number;
actionTimeCurrent: number;
actionTimeOverflow: number;
action: IActionIdentifier;
cities: Record<string, City>;
city: string;
skills: Record<string, number>;
skillMultipliers: Record<string, number>;
staminaBonus: number;
maxStamina: number;
stamina: number;
contracts: Record<string, Contract>;
operations: Record<string, Operation>;
blackops: Record<string, boolean>;
logging: {
general: boolean;
contracts: boolean;
ops: boolean;
blackops: boolean;
events: boolean;
};
automateEnabled: boolean;
automateActionHigh: IActionIdentifier;
automateThreshHigh: number;
automateActionLow: IActionIdentifier;
automateThreshLow: number;
consoleHistory: string[];
consoleLogs: string[];
getCurrentCity(): City;
calculateStaminaPenalty(): number;
startAction(player: IPlayer, action: IActionIdentifier): void;
upgradeSkill(skill: Skill): void;
executeConsoleCommands(player: IPlayer, command: string): void;
postToConsole(input: string, saveToLogs?: boolean): void;
log(input: string): void;
resetAction(): void;
clearConsole(): void;
prestige(): void;
storeCycles(numCycles?: number): void;
getTypeAndNameFromActionId(actionId: IActionIdentifier): {
type: string;
name: string;
};
getContractNamesNetscriptFn(): string[];
getOperationNamesNetscriptFn(): string[];
getBlackOpNamesNetscriptFn(): string[];
getGeneralActionNamesNetscriptFn(): string[];
getSkillNamesNetscriptFn(): string[];
startActionNetscriptFn(player: IPlayer, type: string, name: string, workerScript: WorkerScript): boolean;
getActionTimeNetscriptFn(person: IPerson, type: string, name: string): number | string;
getActionEstimatedSuccessChanceNetscriptFn(person: IPerson, type: string, name: string): [number, number] | string;
getActionCountRemainingNetscriptFn(type: string, name: string, workerScript: WorkerScript): number;
getSkillLevelNetscriptFn(skillName: string, workerScript: WorkerScript): number;
getSkillUpgradeCostNetscriptFn(skillName: string, count: number, workerScript: WorkerScript): number;
upgradeSkillNetscriptFn(skillName: string, count: number, workerScript: WorkerScript): boolean;
getTeamSizeNetscriptFn(type: string, name: string, workerScript: WorkerScript): number;
setTeamSizeNetscriptFn(type: string, name: string, size: number, workerScript: WorkerScript): number;
joinBladeburnerFactionNetscriptFn(workerScript: WorkerScript): boolean;
getActionIdFromTypeAndName(type: string, name: string): IActionIdentifier | null;
executeStartConsoleCommand(player: IPlayer, args: string[]): void;
executeSkillConsoleCommand(args: string[]): void;
executeLogConsoleCommand(args: string[]): void;
executeHelpConsoleCommand(args: string[]): void;
executeAutomateConsoleCommand(args: string[]): void;
parseCommandArguments(command: string): string[];
executeConsoleCommand(player: IPlayer, command: string): void;
triggerMigration(sourceCityName: string): void;
triggerPotentialMigration(sourceCityName: string, chance: number): void;
randomEvent(): void;
getDiplomacyEffectiveness(player: IPlayer): number;
getRecruitmentSuccessChance(player: IPerson): number;
getRecruitmentTime(player: IPerson): number;
resetSkillMultipliers(): void;
updateSkillMultipliers(): void;
completeOperation(success: boolean, player: IPlayer): void;
getActionObject(actionId: IActionIdentifier): IAction | null;
completeContract(success: boolean, actionIdent: IActionIdentifier): void;
completeAction(player: IPlayer, person: IPerson, actionIdent: IActionIdentifier, isPlayer?: boolean): ITaskTracker;
infiltrateSynthoidCommunities(p: IPlayer): void;
changeRank(player: IPlayer, change: number): void;
processAction(router: IRouter, player: IPlayer, seconds: number): void;
calculateStaminaGainPerSecond(player: IPlayer): number;
calculateMaxStamina(player: IPlayer): void;
create(): void;
process(router: IRouter, player: IPlayer): void;
getActionStats(action: IAction, person: IPerson, success: boolean): ITaskTracker;
sleeveSupport(joining: boolean): void;
}

@ -1,4 +1,4 @@
import { IBladeburner } from "./IBladeburner"; import { Bladeburner } from "./Bladeburner";
import { BladeburnerConstants } from "./data/Constants"; import { BladeburnerConstants } from "./data/Constants";
import { Action, IActionParams } from "./Action"; import { Action, IActionParams } from "./Action";
import { Generic_fromJSON, Generic_toJSON, IReviverValue, Reviver } from "../utils/JSONReviver"; import { Generic_fromJSON, Generic_toJSON, IReviverValue, Reviver } from "../utils/JSONReviver";
@ -19,7 +19,7 @@ export class Operation extends Action {
} }
// For actions that have teams. To be implemented by subtypes. // For actions that have teams. To be implemented by subtypes.
getTeamSuccessBonus(inst: IBladeburner): number { getTeamSuccessBonus(inst: Bladeburner): number {
if (this.teamCount && this.teamCount > 0) { if (this.teamCount && this.teamCount > 0) {
this.teamCount = Math.min(this.teamCount, inst.teamSize); this.teamCount = Math.min(this.teamCount, inst.teamSize);
const teamMultiplier = Math.pow(this.teamCount, 0.05); const teamMultiplier = Math.pow(this.teamCount, 0.05);
@ -29,11 +29,11 @@ export class Operation extends Action {
return 1; return 1;
} }
getActionTypeSkillSuccessBonus(inst: IBladeburner): number { getActionTypeSkillSuccessBonus(inst: Bladeburner): number {
return inst.skillMultipliers.successChanceOperation; return inst.skillMultipliers.successChanceOperation;
} }
getChaosDifficultyBonus(inst: IBladeburner /*, params: ISuccessChanceParams*/): number { getChaosDifficultyBonus(inst: Bladeburner /*, params: ISuccessChanceParams*/): number {
const city = inst.getCurrentCity(); const city = inst.getCurrentCity();
if (city.chaos > BladeburnerConstants.ChaosThreshold) { if (city.chaos > BladeburnerConstants.ChaosThreshold) {
const diff = 1 + (city.chaos - BladeburnerConstants.ChaosThreshold); const diff = 1 + (city.chaos - BladeburnerConstants.ChaosThreshold);

@ -1,33 +1,5 @@
import { CityName } from "./../../Locations/data/CityNames"; import { CityName } from "./../../Locations/data/CityNames";
export const BladeburnerConstants: { export const BladeburnerConstants = {
CityNames: string[];
CyclesPerSecond: number;
StaminaGainPerSecond: number;
BaseStaminaLoss: number;
MaxStaminaToGainFactor: number;
DifficultyToTimeFactor: number;
DiffMultExponentialFactor: number;
DiffMultLinearFactor: number;
EffAgiLinearFactor: number;
EffDexLinearFactor: number;
EffAgiExponentialFactor: number;
EffDexExponentialFactor: number;
BaseRecruitmentTimeNeeded: number;
PopulationThreshold: number;
PopulationExponent: number;
ChaosThreshold: number;
BaseStatGain: number;
BaseIntGain: number;
ActionCountGrowthPeriod: number;
RankToFactionRepFactor: number;
RankNeededForFaction: number;
ContractSuccessesPerLevel: number;
OperationSuccessesPerLevel: number;
RanksPerSkillPoint: number;
ContractBaseMoneyGain: number;
HrcHpGain: number;
HrcStaminaGain: number;
} = {
CityNames: [ CityNames: [
CityName.Aevum, CityName.Aevum,
CityName.Chongqing, CityName.Chongqing,

@ -1,19 +1,4 @@
export const SkillNames: { export const SkillNames = {
BladesIntuition: string;
Cloak: string;
Marksman: string;
WeaponProficiency: string;
ShortCircuit: string;
DigitalObserver: string;
Tracer: string;
Overclock: string;
Reaper: string;
EvasiveSystem: string;
Datamancer: string;
CybersEdge: string;
HandsOfMidas: string;
Hyperdrive: string;
} = {
BladesIntuition: "Blade's Intuition", BladesIntuition: "Blade's Intuition",
Cloak: "Cloak", Cloak: "Cloak",
Marksman: "Marksman", Marksman: "Marksman",

@ -1,8 +1,7 @@
import React from "react"; import React from "react";
import { IAction } from "../IAction"; import { Action } from "../Action";
import { IBladeburner } from "../IBladeburner"; import { Bladeburner } from "../Bladeburner";
import { BladeburnerConstants } from "../data/Constants"; import { BladeburnerConstants } from "../data/Constants";
import { use } from "../../ui/Context";
import Typography from "@mui/material/Typography"; import Typography from "@mui/material/Typography";
import Tooltip from "@mui/material/Tooltip"; import Tooltip from "@mui/material/Tooltip";
@ -12,29 +11,27 @@ import ArrowDropUpIcon from "@mui/icons-material/ArrowDropUp";
import ArrowDropDownIcon from "@mui/icons-material/ArrowDropDown"; import ArrowDropDownIcon from "@mui/icons-material/ArrowDropDown";
interface IProps { interface IProps {
action: IAction; action: Action;
isActive: boolean; isActive: boolean;
bladeburner: IBladeburner; bladeburner: Bladeburner;
rerender: () => void; rerender: () => void;
} }
export function ActionLevel({ action, isActive, bladeburner, rerender }: IProps): React.ReactElement { export function ActionLevel({ action, isActive, bladeburner, rerender }: IProps): React.ReactElement {
const player = use.Player();
const canIncrease = action.level < action.maxLevel; const canIncrease = action.level < action.maxLevel;
const canDecrease = action.level > 1; const canDecrease = action.level > 1;
function increaseLevel(): void { function increaseLevel(): void {
if (!canIncrease) return; if (!canIncrease) return;
++action.level; ++action.level;
if (isActive) bladeburner.startAction(player, bladeburner.action); if (isActive) bladeburner.startAction(bladeburner.action);
rerender(); rerender();
} }
function decreaseLevel(): void { function decreaseLevel(): void {
if (!canDecrease) return; if (!canDecrease) return;
--action.level; --action.level;
if (isActive) bladeburner.startAction(player, bladeburner.action); if (isActive) bladeburner.startAction(bladeburner.action);
rerender(); rerender();
} }

@ -4,16 +4,14 @@ import { ContractPage } from "./ContractPage";
import { OperationPage } from "./OperationPage"; import { OperationPage } from "./OperationPage";
import { BlackOpPage } from "./BlackOpPage"; import { BlackOpPage } from "./BlackOpPage";
import { SkillPage } from "./SkillPage"; import { SkillPage } from "./SkillPage";
import { IBladeburner } from "../IBladeburner"; import { Bladeburner } from "../Bladeburner";
import { IPlayer } from "../../PersonObjects/IPlayer";
import Tabs from "@mui/material/Tabs"; import Tabs from "@mui/material/Tabs";
import Tab from "@mui/material/Tab"; import Tab from "@mui/material/Tab";
import Box from "@mui/material/Box"; import Box from "@mui/material/Box";
interface IProps { interface IProps {
bladeburner: IBladeburner; bladeburner: Bladeburner;
player: IPlayer;
} }
export function AllPages(props: IProps): React.ReactElement { export function AllPages(props: IProps): React.ReactElement {
@ -33,10 +31,10 @@ export function AllPages(props: IProps): React.ReactElement {
<Tab label="Skills" /> <Tab label="Skills" />
</Tabs> </Tabs>
<Box sx={{ p: 1 }}> <Box sx={{ p: 1 }}>
{value === 0 && <GeneralActionPage bladeburner={props.bladeburner} player={props.player} />} {value === 0 && <GeneralActionPage bladeburner={props.bladeburner} />}
{value === 1 && <ContractPage bladeburner={props.bladeburner} player={props.player} />} {value === 1 && <ContractPage bladeburner={props.bladeburner} />}
{value === 2 && <OperationPage bladeburner={props.bladeburner} player={props.player} />} {value === 2 && <OperationPage bladeburner={props.bladeburner} />}
{value === 3 && <BlackOpPage bladeburner={props.bladeburner} player={props.player} />} {value === 3 && <BlackOpPage bladeburner={props.bladeburner} />}
{value === 4 && <SkillPage bladeburner={props.bladeburner} />} {value === 4 && <SkillPage bladeburner={props.bladeburner} />}
</Box> </Box>
</> </>

@ -1,12 +1,12 @@
import React from "react"; import React from "react";
import { IAction } from "../IAction"; import { Action } from "../Action";
import Typography from "@mui/material/Typography"; import Typography from "@mui/material/Typography";
import Tooltip from "@mui/material/Tooltip"; import Tooltip from "@mui/material/Tooltip";
import Box from "@mui/material/Box"; import Box from "@mui/material/Box";
import Switch from "@mui/material/Switch"; import Switch from "@mui/material/Switch";
interface IProps { interface IProps {
action: IAction; action: Action;
rerender: () => void; rerender: () => void;
} }

@ -3,10 +3,10 @@ import { formatNumber, convertTimeMsToTimeElapsedString } from "../../utils/Stri
import { ActionTypes } from "../data/ActionTypes"; import { ActionTypes } from "../data/ActionTypes";
import { createProgressBarText } from "../../utils/helpers/createProgressBarText"; import { createProgressBarText } from "../../utils/helpers/createProgressBarText";
import { TeamSizeButton } from "./TeamSizeButton"; import { TeamSizeButton } from "./TeamSizeButton";
import { IBladeburner } from "../IBladeburner"; import { Bladeburner } from "../Bladeburner";
import { BlackOperation } from "../BlackOperation"; import { BlackOperation } from "../BlackOperation";
import { BlackOperations } from "../data/BlackOperations"; import { BlackOperations } from "../data/BlackOperations";
import { IPlayer } from "../../PersonObjects/IPlayer"; import { Player } from "../../Player";
import { CopyableText } from "../../ui/React/CopyableText"; import { CopyableText } from "../../ui/React/CopyableText";
import { SuccessChance } from "./SuccessChance"; import { SuccessChance } from "./SuccessChance";
import { StartButton } from "./StartButton"; import { StartButton } from "./StartButton";
@ -15,8 +15,7 @@ import Typography from "@mui/material/Typography";
import Paper from "@mui/material/Paper"; import Paper from "@mui/material/Paper";
interface IProps { interface IProps {
bladeburner: IBladeburner; bladeburner: Bladeburner;
player: IPlayer;
action: BlackOperation; action: BlackOperation;
} }
@ -37,7 +36,7 @@ export function BlackOpElem(props: IProps): React.ReactElement {
const isActive = const isActive =
props.bladeburner.action.type === ActionTypes["BlackOperation"] && props.bladeburner.action.type === ActionTypes["BlackOperation"] &&
props.action.name === props.bladeburner.action.name; props.action.name === props.bladeburner.action.name;
const actionTime = props.action.getActionTime(props.bladeburner, props.player); const actionTime = props.action.getActionTime(props.bladeburner, Player);
const hasReqdRank = props.bladeburner.rank >= props.action.reqdRank; const hasReqdRank = props.bladeburner.rank >= props.action.reqdRank;
const computedActionTimeCurrent = Math.min( const computedActionTimeCurrent = Math.min(
props.bladeburner.actionTimeCurrent + props.bladeburner.actionTimeOverflow, props.bladeburner.actionTimeCurrent + props.bladeburner.actionTimeOverflow,

@ -2,12 +2,10 @@ import React from "react";
import { BlackOperations } from "../BlackOperations"; import { BlackOperations } from "../BlackOperations";
import { BlackOperation } from "../BlackOperation"; import { BlackOperation } from "../BlackOperation";
import { BlackOpElem } from "./BlackOpElem"; import { BlackOpElem } from "./BlackOpElem";
import { IBladeburner } from "../IBladeburner"; import { Bladeburner } from "../Bladeburner";
import { IPlayer } from "../../PersonObjects/IPlayer";
interface IProps { interface IProps {
bladeburner: IBladeburner; bladeburner: Bladeburner;
player: IPlayer;
} }
export function BlackOpList(props: IProps): React.ReactElement { export function BlackOpList(props: IProps): React.ReactElement {
@ -35,7 +33,7 @@ export function BlackOpList(props: IProps): React.ReactElement {
return ( return (
<> <>
{blackops.map((blackop: BlackOperation) => ( {blackops.map((blackop: BlackOperation) => (
<BlackOpElem key={blackop.name} bladeburner={props.bladeburner} action={blackop} player={props.player} /> <BlackOpElem key={blackop.name} bladeburner={props.bladeburner} action={blackop} />
))} ))}
</> </>
); );

@ -1,21 +1,18 @@
import * as React from "react"; import * as React from "react";
import { BlackOpList } from "./BlackOpList"; import { BlackOpList } from "./BlackOpList";
import { IBladeburner } from "../IBladeburner"; import { Bladeburner } from "../Bladeburner";
import { IPlayer } from "../../PersonObjects/IPlayer";
import Typography from "@mui/material/Typography"; import Typography from "@mui/material/Typography";
import { FactionNames } from "../../Faction/data/FactionNames"; import { FactionNames } from "../../Faction/data/FactionNames";
import { use } from "../../ui/Context"; import { Router } from "../../ui/GameRoot";
import { BlackOperationNames } from "../data/BlackOperationNames"; import { BlackOperationNames } from "../data/BlackOperationNames";
import { Button } from "@mui/material"; import { Button } from "@mui/material";
import { CorruptableText } from "../../ui/React/CorruptableText"; import { CorruptableText } from "../../ui/React/CorruptableText";
interface IProps { interface IProps {
bladeburner: IBladeburner; bladeburner: Bladeburner;
player: IPlayer;
} }
export function BlackOpPage(props: IProps): React.ReactElement { export function BlackOpPage(props: IProps): React.ReactElement {
const router = use.Router();
return ( return (
<> <>
<Typography> <Typography>
@ -33,11 +30,11 @@ export function BlackOpPage(props: IProps): React.ReactElement {
losses. losses.
</Typography> </Typography>
{props.bladeburner.blackops[BlackOperationNames.OperationDaedalus] ? ( {props.bladeburner.blackops[BlackOperationNames.OperationDaedalus] ? (
<Button sx={{ my: 1, p: 1 }} onClick={() => router.toBitVerse(false, false)}> <Button sx={{ my: 1, p: 1 }} onClick={() => Router.toBitVerse(false, false)}>
<CorruptableText content="Destroy w0rld_d34mon"></CorruptableText> <CorruptableText content="Destroy w0rld_d34mon"></CorruptableText>
</Button> </Button>
) : ( ) : (
<BlackOpList bladeburner={props.bladeburner} player={props.player} /> <BlackOpList bladeburner={props.bladeburner} />
)} )}
</> </>
); );

@ -1,11 +1,10 @@
import React from "react"; import React from "react";
import { FactionNames } from "../../Faction/data/FactionNames"; import { FactionNames } from "../../Faction/data/FactionNames";
import { use } from "../../ui/Context"; import { Router } from "../../ui/GameRoot";
import { CinematicText } from "../../ui/React/CinematicText"; import { CinematicText } from "../../ui/React/CinematicText";
import { dialogBoxCreate } from "../../ui/React/DialogBox"; import { dialogBoxCreate } from "../../ui/React/DialogBox";
export function BladeburnerCinematic(): React.ReactElement { export function BladeburnerCinematic(): React.ReactElement {
const router = use.Router();
return ( return (
<CinematicText <CinematicText
lines={[ lines={[
@ -32,7 +31,7 @@ export function BladeburnerCinematic(): React.ReactElement {
"investigating and dealing with Synthoid threats.", "investigating and dealing with Synthoid threats.",
]} ]}
onDone={() => { onDone={() => {
router.toTerminal(); Router.toTerminal();
dialogBoxCreate( dialogBoxCreate(
`Visit the National Security Agency (NSA) to apply for their ${FactionNames.Bladeburners} ` + `Visit the National Security Agency (NSA) to apply for their ${FactionNames.Bladeburners} ` +
"division! You will need 100 of each combat stat before doing this.", "division! You will need 100 of each combat stat before doing this.",

@ -3,12 +3,10 @@ import { Stats } from "./Stats";
import { Console } from "./Console"; import { Console } from "./Console";
import { AllPages } from "./AllPages"; import { AllPages } from "./AllPages";
import { use } from "../../ui/Context"; import { Player } from "../../Player";
import Box from "@mui/material/Box"; import Box from "@mui/material/Box";
export function BladeburnerRoot(): React.ReactElement { export function BladeburnerRoot(): React.ReactElement {
const player = use.Player();
const router = use.Router();
const setRerender = useState(false)[1]; const setRerender = useState(false)[1];
function rerender(): void { function rerender(): void {
setRerender((old) => !old); setRerender((old) => !old);
@ -19,16 +17,16 @@ export function BladeburnerRoot(): React.ReactElement {
return () => clearInterval(id); return () => clearInterval(id);
}, []); }, []);
const bladeburner = player.bladeburner; const bladeburner = Player.bladeburner;
if (bladeburner === null) return <></>; if (!bladeburner) return <></>;
return ( return (
<Box display="flex" flexDirection="column"> <Box display="flex" flexDirection="column">
<Box sx={{ display: "grid", gridTemplateColumns: "4fr 8fr", p: 1 }}> <Box sx={{ display: "grid", gridTemplateColumns: "4fr 8fr", p: 1 }}>
<Stats bladeburner={bladeburner} player={player} router={router} /> <Stats bladeburner={bladeburner} />
<Console bladeburner={bladeburner} player={player} /> <Console bladeburner={bladeburner} />
</Box> </Box>
<AllPages bladeburner={bladeburner} player={player} /> <AllPages bladeburner={bladeburner} />
</Box> </Box>
); );
} }

@ -1,8 +1,7 @@
import React, { useState, useRef, useEffect } from "react"; import React, { useState, useRef, useEffect } from "react";
import { IBladeburner } from "../IBladeburner"; import { Bladeburner } from "../Bladeburner";
import { KEY } from "../../utils/helpers/keyCodes"; import { KEY } from "../../utils/helpers/keyCodes";
import { IPlayer } from "../../PersonObjects/IPlayer";
import Paper from "@mui/material/Paper"; import Paper from "@mui/material/Paper";
import List from "@mui/material/List"; import List from "@mui/material/List";
import ListItem from "@mui/material/ListItem"; import ListItem from "@mui/material/ListItem";
@ -49,8 +48,7 @@ function Line(props: ILineProps): React.ReactElement {
} }
interface IProps { interface IProps {
bladeburner: IBladeburner; bladeburner: Bladeburner;
player: IPlayer;
} }
export function Console(props: IProps): React.ReactElement { export function Console(props: IProps): React.ReactElement {
@ -81,7 +79,7 @@ export function Console(props: IProps): React.ReactElement {
event.preventDefault(); event.preventDefault();
if (command.length > 0) { if (command.length > 0) {
props.bladeburner.postToConsole("> " + command); props.bladeburner.postToConsole("> " + command);
props.bladeburner.executeConsoleCommands(props.player, command); props.bladeburner.executeConsoleCommands(command);
setConsoleHistoryIndex(props.bladeburner.consoleHistory.length); setConsoleHistoryIndex(props.bladeburner.consoleHistory.length);
setCommand(""); setCommand("");
} }

@ -3,9 +3,9 @@ import { ActionTypes } from "../data/ActionTypes";
import { createProgressBarText } from "../../utils/helpers/createProgressBarText"; import { createProgressBarText } from "../../utils/helpers/createProgressBarText";
import { formatNumber, convertTimeMsToTimeElapsedString } from "../../utils/StringHelperFunctions"; import { formatNumber, convertTimeMsToTimeElapsedString } from "../../utils/StringHelperFunctions";
import { Contracts } from "../data/Contracts"; import { Contracts } from "../data/Contracts";
import { IBladeburner } from "../IBladeburner"; import { Bladeburner } from "../Bladeburner";
import { IAction } from "../IAction"; import { Action } from "../Action";
import { IPlayer } from "../../PersonObjects/IPlayer"; import { Player } from "../../Player";
import { SuccessChance } from "./SuccessChance"; import { SuccessChance } from "./SuccessChance";
import { CopyableText } from "../../ui/React/CopyableText"; import { CopyableText } from "../../ui/React/CopyableText";
import { ActionLevel } from "./ActionLevel"; import { ActionLevel } from "./ActionLevel";
@ -16,9 +16,8 @@ import Typography from "@mui/material/Typography";
import Paper from "@mui/material/Paper"; import Paper from "@mui/material/Paper";
interface IProps { interface IProps {
bladeburner: IBladeburner; bladeburner: Bladeburner;
player: IPlayer; action: Action;
action: IAction;
} }
export function ContractElem(props: IProps): React.ReactElement { export function ContractElem(props: IProps): React.ReactElement {
@ -32,7 +31,7 @@ export function ContractElem(props: IProps): React.ReactElement {
props.bladeburner.actionTimeCurrent + props.bladeburner.actionTimeOverflow, props.bladeburner.actionTimeCurrent + props.bladeburner.actionTimeOverflow,
props.bladeburner.actionTimeToComplete, props.bladeburner.actionTimeToComplete,
); );
const actionTime = props.action.getActionTime(props.bladeburner, props.player); const actionTime = props.action.getActionTime(props.bladeburner, Player);
const actionData = Contracts[props.action.name]; const actionData = Contracts[props.action.name];
if (actionData === undefined) { if (actionData === undefined) {

@ -1,11 +1,9 @@
import React from "react"; import React from "react";
import { ContractElem } from "./ContractElem"; import { ContractElem } from "./ContractElem";
import { IBladeburner } from "../IBladeburner"; import { Bladeburner } from "../Bladeburner";
import { IPlayer } from "../../PersonObjects/IPlayer";
interface IProps { interface IProps {
bladeburner: IBladeburner; bladeburner: Bladeburner;
player: IPlayer;
} }
export function ContractList(props: IProps): React.ReactElement { export function ContractList(props: IProps): React.ReactElement {
@ -14,7 +12,7 @@ export function ContractList(props: IProps): React.ReactElement {
return ( return (
<> <>
{names.map((name: string) => ( {names.map((name: string) => (
<ContractElem key={name} bladeburner={props.bladeburner} action={contracts[name]} player={props.player} /> <ContractElem key={name} bladeburner={props.bladeburner} action={contracts[name]} />
))} ))}
</> </>
); );

@ -1,12 +1,10 @@
import * as React from "react"; import * as React from "react";
import { ContractList } from "./ContractList"; import { ContractList } from "./ContractList";
import { IBladeburner } from "../IBladeburner"; import { Bladeburner } from "../Bladeburner";
import { IPlayer } from "../../PersonObjects/IPlayer";
import Typography from "@mui/material/Typography"; import Typography from "@mui/material/Typography";
interface IProps { interface IProps {
bladeburner: IBladeburner; bladeburner: Bladeburner;
player: IPlayer;
} }
export function ContractPage(props: IProps): React.ReactElement { export function ContractPage(props: IProps): React.ReactElement {
@ -20,7 +18,7 @@ export function ContractPage(props: IProps): React.ReactElement {
You can unlock higher-level contracts by successfully completing them. Higher-level contracts are more You can unlock higher-level contracts by successfully completing them. Higher-level contracts are more
difficult, but grant more rank, experience, and money. difficult, but grant more rank, experience, and money.
</Typography> </Typography>
<ContractList bladeburner={props.bladeburner} player={props.player} /> <ContractList bladeburner={props.bladeburner} />
</> </>
); );
} }

@ -2,10 +2,10 @@ import React, { useState } from "react";
import { ActionTypes } from "../data/ActionTypes"; import { ActionTypes } from "../data/ActionTypes";
import { createProgressBarText } from "../../utils/helpers/createProgressBarText"; import { createProgressBarText } from "../../utils/helpers/createProgressBarText";
import { formatNumber, convertTimeMsToTimeElapsedString } from "../../utils/StringHelperFunctions"; import { formatNumber, convertTimeMsToTimeElapsedString } from "../../utils/StringHelperFunctions";
import { IBladeburner } from "../IBladeburner"; import { Bladeburner } from "../Bladeburner";
import { IAction } from "../IAction"; import { Action } from "../Action";
import { GeneralActions } from "../data/GeneralActions"; import { GeneralActions } from "../data/GeneralActions";
import { IPlayer } from "../../PersonObjects/IPlayer"; import { Player } from "../../Player";
import { CopyableText } from "../../ui/React/CopyableText"; import { CopyableText } from "../../ui/React/CopyableText";
import { StartButton } from "./StartButton"; import { StartButton } from "./StartButton";
@ -15,9 +15,8 @@ import Box from "@mui/material/Box";
import Paper from "@mui/material/Paper"; import Paper from "@mui/material/Paper";
interface IProps { interface IProps {
bladeburner: IBladeburner; bladeburner: Bladeburner;
player: IPlayer; action: Action;
action: IAction;
} }
export function GeneralActionElem(props: IProps): React.ReactElement { export function GeneralActionElem(props: IProps): React.ReactElement {
@ -40,13 +39,13 @@ export function GeneralActionElem(props: IProps): React.ReactElement {
case "Incite Violence": case "Incite Violence":
return 60; return 60;
case "Recruitment": case "Recruitment":
return props.bladeburner.getRecruitmentTime(props.player); return props.bladeburner.getRecruitmentTime(Player);
} }
return -1; // dead code return -1; // dead code
})(); })();
const successChance = const successChance =
props.action.name === "Recruitment" props.action.name === "Recruitment"
? Math.max(0, Math.min(props.bladeburner.getRecruitmentSuccessChance(props.player), 1)) ? Math.max(0, Math.min(props.bladeburner.getRecruitmentSuccessChance(Player), 1))
: -1; : -1;
const actionData = GeneralActions[props.action.name]; const actionData = GeneralActions[props.action.name];

@ -2,12 +2,10 @@ import React from "react";
import { GeneralActionElem } from "./GeneralActionElem"; import { GeneralActionElem } from "./GeneralActionElem";
import { Action } from "../Action"; import { Action } from "../Action";
import { GeneralActions } from "../GeneralActions"; import { GeneralActions } from "../GeneralActions";
import { IBladeburner } from "../IBladeburner"; import { Bladeburner } from "../Bladeburner";
import { IPlayer } from "../../PersonObjects/IPlayer";
interface IProps { interface IProps {
bladeburner: IBladeburner; bladeburner: Bladeburner;
player: IPlayer;
} }
export function GeneralActionList(props: IProps): React.ReactElement { export function GeneralActionList(props: IProps): React.ReactElement {
@ -20,7 +18,7 @@ export function GeneralActionList(props: IProps): React.ReactElement {
return ( return (
<> <>
{actions.map((action: Action) => ( {actions.map((action: Action) => (
<GeneralActionElem key={action.name} bladeburner={props.bladeburner} action={action} player={props.player} /> <GeneralActionElem key={action.name} bladeburner={props.bladeburner} action={action} />
))} ))}
</> </>
); );

@ -1,19 +1,17 @@
import * as React from "react"; import * as React from "react";
import { GeneralActionList } from "./GeneralActionList"; import { GeneralActionList } from "./GeneralActionList";
import { IBladeburner } from "../IBladeburner"; import { Bladeburner } from "../Bladeburner";
import { IPlayer } from "../../PersonObjects/IPlayer";
import Typography from "@mui/material/Typography"; import Typography from "@mui/material/Typography";
interface IProps { interface IProps {
bladeburner: IBladeburner; bladeburner: Bladeburner;
player: IPlayer;
} }
export function GeneralActionPage(props: IProps): React.ReactElement { export function GeneralActionPage(props: IProps): React.ReactElement {
return ( return (
<> <>
<Typography>These are generic actions that will assist you in your Bladeburner duties.</Typography> <Typography>These are generic actions that will assist you in your Bladeburner duties.</Typography>
<GeneralActionList bladeburner={props.bladeburner} player={props.player} /> <GeneralActionList bladeburner={props.bladeburner} />
</> </>
); );
} }

@ -7,18 +7,17 @@ import { ActionLevel } from "./ActionLevel";
import { Autolevel } from "./Autolevel"; import { Autolevel } from "./Autolevel";
import { StartButton } from "./StartButton"; import { StartButton } from "./StartButton";
import { TeamSizeButton } from "./TeamSizeButton"; import { TeamSizeButton } from "./TeamSizeButton";
import { IBladeburner } from "../IBladeburner"; import { Bladeburner } from "../Bladeburner";
import { Operation } from "../Operation"; import { Operation } from "../Operation";
import { Operations } from "../data/Operations"; import { Operations } from "../data/Operations";
import { IPlayer } from "../../PersonObjects/IPlayer"; import { Player } from "../../Player";
import { CopyableText } from "../../ui/React/CopyableText"; import { CopyableText } from "../../ui/React/CopyableText";
import Typography from "@mui/material/Typography"; import Typography from "@mui/material/Typography";
import Paper from "@mui/material/Paper"; import Paper from "@mui/material/Paper";
interface IProps { interface IProps {
bladeburner: IBladeburner; bladeburner: Bladeburner;
player: IPlayer;
action: Operation; action: Operation;
} }
@ -33,7 +32,7 @@ export function OperationElem(props: IProps): React.ReactElement {
props.bladeburner.actionTimeCurrent + props.bladeburner.actionTimeOverflow, props.bladeburner.actionTimeCurrent + props.bladeburner.actionTimeOverflow,
props.bladeburner.actionTimeToComplete, props.bladeburner.actionTimeToComplete,
); );
const actionTime = props.action.getActionTime(props.bladeburner, props.player); const actionTime = props.action.getActionTime(props.bladeburner, Player);
const actionData = Operations[props.action.name]; const actionData = Operations[props.action.name];
if (actionData === undefined) { if (actionData === undefined) {

@ -1,11 +1,9 @@
import React from "react"; import React from "react";
import { OperationElem } from "./OperationElem"; import { OperationElem } from "./OperationElem";
import { IBladeburner } from "../IBladeburner"; import { Bladeburner } from "../Bladeburner";
import { IPlayer } from "../../PersonObjects/IPlayer";
interface IProps { interface IProps {
bladeburner: IBladeburner; bladeburner: Bladeburner;
player: IPlayer;
} }
export function OperationList(props: IProps): React.ReactElement { export function OperationList(props: IProps): React.ReactElement {
@ -14,7 +12,7 @@ export function OperationList(props: IProps): React.ReactElement {
return ( return (
<> <>
{names.map((name: string) => ( {names.map((name: string) => (
<OperationElem key={name} bladeburner={props.bladeburner} action={operations[name]} player={props.player} /> <OperationElem key={name} bladeburner={props.bladeburner} action={operations[name]} />
))} ))}
</> </>
); );

@ -1,12 +1,10 @@
import * as React from "react"; import * as React from "react";
import { OperationList } from "./OperationList"; import { OperationList } from "./OperationList";
import { IBladeburner } from "../IBladeburner"; import { Bladeburner } from "../Bladeburner";
import { IPlayer } from "../../PersonObjects/IPlayer";
import Typography from "@mui/material/Typography"; import Typography from "@mui/material/Typography";
interface IProps { interface IProps {
bladeburner: IBladeburner; bladeburner: Bladeburner;
player: IPlayer;
} }
export function OperationPage(props: IProps): React.ReactElement { export function OperationPage(props: IProps): React.ReactElement {
@ -29,7 +27,7 @@ export function OperationPage(props: IProps): React.ReactElement {
You can unlock higher-level operations by successfully completing them. Higher-level operations are more You can unlock higher-level operations by successfully completing them. Higher-level operations are more
difficult, but grant more rank and experience. difficult, but grant more rank and experience.
</Typography> </Typography>
<OperationList bladeburner={props.bladeburner} player={props.player} /> <OperationList bladeburner={props.bladeburner} />
</> </>
); );
} }

@ -1,7 +1,7 @@
import React from "react"; import React from "react";
import { CopyableText } from "../../ui/React/CopyableText"; import { CopyableText } from "../../ui/React/CopyableText";
import { formatNumber } from "../../utils/StringHelperFunctions"; import { formatNumber } from "../../utils/StringHelperFunctions";
import { IBladeburner } from "../IBladeburner"; import { Bladeburner } from "../Bladeburner";
import Typography from "@mui/material/Typography"; import Typography from "@mui/material/Typography";
import IconButton from "@mui/material/IconButton"; import IconButton from "@mui/material/IconButton";
@ -13,7 +13,7 @@ import { Skill } from "../Skill";
interface IProps { interface IProps {
skill: Skill; skill: Skill;
bladeburner: IBladeburner; bladeburner: Bladeburner;
onUpgrade: () => void; onUpgrade: () => void;
} }

@ -1,10 +1,10 @@
import * as React from "react"; import * as React from "react";
import { SkillElem } from "./SkillElem"; import { SkillElem } from "./SkillElem";
import { Skills } from "../Skills"; import { Skills } from "../Skills";
import { IBladeburner } from "../IBladeburner"; import { Bladeburner } from "../Bladeburner";
interface IProps { interface IProps {
bladeburner: IBladeburner; bladeburner: Bladeburner;
onUpgrade: () => void; onUpgrade: () => void;
} }

@ -2,10 +2,10 @@ import React, { useState } from "react";
import { SkillList } from "./SkillList"; import { SkillList } from "./SkillList";
import { BladeburnerConstants } from "../data/Constants"; import { BladeburnerConstants } from "../data/Constants";
import { formatNumber } from "../../utils/StringHelperFunctions"; import { formatNumber } from "../../utils/StringHelperFunctions";
import { IBladeburner } from "../IBladeburner"; import { Bladeburner } from "../Bladeburner";
import Typography from "@mui/material/Typography"; import Typography from "@mui/material/Typography";
interface IProps { interface IProps {
bladeburner: IBladeburner; bladeburner: Bladeburner;
} }
export function SkillPage(props: IProps): React.ReactElement { export function SkillPage(props: IProps): React.ReactElement {

@ -1,20 +1,20 @@
import React from "react"; import React from "react";
import { IBladeburner } from "../IBladeburner"; import { Bladeburner } from "../Bladeburner";
import { BlackOperation } from "../BlackOperation"; import { BlackOperation } from "../BlackOperation";
import { use } from "../../ui/Context"; import { Player } from "../../Player";
import Button from "@mui/material/Button"; import Button from "@mui/material/Button";
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames"; import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
import { ActionIdentifier } from "../ActionIdentifier";
interface IProps { interface IProps {
bladeburner: IBladeburner; bladeburner: Bladeburner;
type: number; type: number;
name: string; name: string;
rerender: () => void; rerender: () => void;
} }
export function StartButton(props: IProps): React.ReactElement { export function StartButton(props: IProps): React.ReactElement {
const player = use.Player(); const action = props.bladeburner.getActionObject(new ActionIdentifier({ name: props.name, type: props.type }));
const action = props.bladeburner.getActionObject({ name: props.name, type: props.type });
if (action == null) { if (action == null) {
throw new Error("Failed to get Operation Object for: " + props.name); throw new Error("Failed to get Operation Object for: " + props.name);
} }
@ -33,8 +33,8 @@ export function StartButton(props: IProps): React.ReactElement {
if (disabled) return; if (disabled) return;
props.bladeburner.action.type = props.type; props.bladeburner.action.type = props.type;
props.bladeburner.action.name = props.name; props.bladeburner.action.name = props.name;
if (!player.hasAugmentation(AugmentationNames.BladesSimulacrum, true)) player.finishWork(true); if (!Player.hasAugmentation(AugmentationNames.BladesSimulacrum, true)) Player.finishWork(true);
props.bladeburner.startAction(player, props.bladeburner.action); props.bladeburner.startAction(props.bladeburner.action);
props.rerender(); props.rerender();
} }

@ -1,13 +1,13 @@
import React, { useState, useEffect } from "react"; import React, { useState, useEffect } from "react";
import { formatNumber, convertTimeMsToTimeElapsedString } from "../../utils/StringHelperFunctions"; import { formatNumber, convertTimeMsToTimeElapsedString } from "../../utils/StringHelperFunctions";
import { BladeburnerConstants } from "../data/Constants"; import { BladeburnerConstants } from "../data/Constants";
import { IPlayer } from "../../PersonObjects/IPlayer"; import { Player } from "../../Player";
import { Money } from "../../ui/React/Money"; import { Money } from "../../ui/React/Money";
import { numeralWrapper } from "../../ui/numeralFormat"; import { numeralWrapper } from "../../ui/numeralFormat";
import { Factions } from "../../Faction/Factions"; import { Factions } from "../../Faction/Factions";
import { IRouter } from "../../ui/Router"; import { Router } from "../../ui/GameRoot";
import { joinFaction } from "../../Faction/FactionHelpers"; import { joinFaction } from "../../Faction/FactionHelpers";
import { IBladeburner } from "../IBladeburner"; import { Bladeburner } from "../Bladeburner";
import { TravelModal } from "./TravelModal"; import { TravelModal } from "./TravelModal";
import Typography from "@mui/material/Typography"; import Typography from "@mui/material/Typography";
@ -18,9 +18,7 @@ import Paper from "@mui/material/Paper";
import { FactionNames } from "../../Faction/data/FactionNames"; import { FactionNames } from "../../Faction/data/FactionNames";
interface IProps { interface IProps {
bladeburner: IBladeburner; bladeburner: Bladeburner;
router: IRouter;
player: IPlayer;
} }
export function Stats(props: IProps): React.ReactElement { export function Stats(props: IProps): React.ReactElement {
@ -40,7 +38,7 @@ export function Stats(props: IProps): React.ReactElement {
joinFaction(faction); joinFaction(faction);
} }
props.router.toFaction(faction); Router.toFaction(faction);
} }
return ( return (
@ -170,13 +168,13 @@ export function Stats(props: IProps): React.ReactElement {
<Typography>Skill Points: {formatNumber(props.bladeburner.skillPoints, 0)}</Typography> <Typography>Skill Points: {formatNumber(props.bladeburner.skillPoints, 0)}</Typography>
<br /> <br />
<Typography> <Typography>
Aug. Success Chance mult: {formatNumber(props.player.mults.bladeburner_success_chance * 100, 1)}% Aug. Success Chance mult: {formatNumber(Player.mults.bladeburner_success_chance * 100, 1)}%
<br /> <br />
Aug. Max Stamina mult: {formatNumber(props.player.mults.bladeburner_max_stamina * 100, 1)}% Aug. Max Stamina mult: {formatNumber(Player.mults.bladeburner_max_stamina * 100, 1)}%
<br /> <br />
Aug. Stamina Gain mult: {formatNumber(props.player.mults.bladeburner_stamina_gain * 100, 1)}% Aug. Stamina Gain mult: {formatNumber(Player.mults.bladeburner_stamina_gain * 100, 1)}%
<br /> <br />
Aug. Field Analysis mult: {formatNumber(props.player.mults.bladeburner_analysis * 100, 1)}% Aug. Field Analysis mult: {formatNumber(Player.mults.bladeburner_analysis * 100, 1)}%
</Typography> </Typography>
</Box> </Box>
</Paper> </Paper>

@ -2,13 +2,13 @@ import React from "react";
import { formatNumber } from "../../utils/StringHelperFunctions"; import { formatNumber } from "../../utils/StringHelperFunctions";
import { StealthIcon } from "./StealthIcon"; import { StealthIcon } from "./StealthIcon";
import { KillIcon } from "./KillIcon"; import { KillIcon } from "./KillIcon";
import { IAction } from "../IAction"; import { Action } from "../Action";
import { IBladeburner } from "../IBladeburner"; import { Bladeburner } from "../Bladeburner";
import { Player } from "../../Player"; import { Player } from "../../Player";
interface IProps { interface IProps {
bladeburner: IBladeburner; bladeburner: Bladeburner;
action: IAction; action: Action;
} }
export function SuccessChance(props: IProps): React.ReactElement { export function SuccessChance(props: IProps): React.ReactElement {

@ -1,12 +1,12 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { Operation } from "../Operation"; import { Operation } from "../Operation";
import { IBladeburner } from "../IBladeburner"; import { Bladeburner } from "../Bladeburner";
import { TeamSizeModal } from "./TeamSizeModal"; import { TeamSizeModal } from "./TeamSizeModal";
import { formatNumber } from "../../utils/StringHelperFunctions"; import { formatNumber } from "../../utils/StringHelperFunctions";
import Button from "@mui/material/Button"; import Button from "@mui/material/Button";
interface IProps { interface IProps {
action: Operation; action: Operation;
bladeburner: IBladeburner; bladeburner: Bladeburner;
} }
export function TeamSizeButton(props: IProps): React.ReactElement { export function TeamSizeButton(props: IProps): React.ReactElement {
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);

@ -2,13 +2,13 @@ import React, { useState } from "react";
import { dialogBoxCreate } from "../../ui/React/DialogBox"; import { dialogBoxCreate } from "../../ui/React/DialogBox";
import { Modal } from "../../ui/React/Modal"; import { Modal } from "../../ui/React/Modal";
import { Action } from "../Action"; import { Action } from "../Action";
import { IBladeburner } from "../IBladeburner"; import { Bladeburner } from "../Bladeburner";
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 TextField from "@mui/material/TextField"; import TextField from "@mui/material/TextField";
interface IProps { interface IProps {
bladeburner: IBladeburner; bladeburner: Bladeburner;
action: Action; action: Action;
open: boolean; open: boolean;
onClose: () => void; onClose: () => void;

@ -1,5 +1,5 @@
import React from "react"; import React from "react";
import { IBladeburner } from "../IBladeburner"; import { Bladeburner } from "../Bladeburner";
import { WorldMap } from "../../ui/React/WorldMap"; import { WorldMap } from "../../ui/React/WorldMap";
import { Modal } from "../../ui/React/Modal"; import { Modal } from "../../ui/React/Modal";
import { CityName } from "../../Locations/data/CityNames"; import { CityName } from "../../Locations/data/CityNames";
@ -8,7 +8,7 @@ import Typography from "@mui/material/Typography";
import Button from "@mui/material/Button"; import Button from "@mui/material/Button";
interface IProps { interface IProps {
bladeburner: IBladeburner; bladeburner: Bladeburner;
open: boolean; open: boolean;
onClose: () => void; onClose: () => void;
} }

@ -1,8 +1,8 @@
import * as React from "react"; import * as React from "react";
import { IPlayer } from "../PersonObjects/IPlayer"; import { Player } from "../Player";
import { Money } from "../ui/React/Money"; import { Money } from "../ui/React/Money";
import { Game, reachedLimit } from "./Game"; import { win, reachedLimit } from "./Game";
import { Deck } from "./CardDeck/Deck"; import { Deck } from "./CardDeck/Deck";
import { Hand } from "./CardDeck/Hand"; import { Hand } from "./CardDeck/Hand";
import { InputAdornment } from "@mui/material"; import { InputAdornment } from "@mui/material";
@ -24,10 +24,6 @@ enum Result {
Tie = "Push! (Tie)", Tie = "Push! (Tie)",
} }
type Props = {
p: IPlayer;
};
type State = { type State = {
playerHand: Hand; playerHand: Hand;
dealerHand: Hand; dealerHand: Hand;
@ -40,11 +36,11 @@ type State = {
wagerInvalidHelperText: string; wagerInvalidHelperText: string;
}; };
export class Blackjack extends Game<Props, State> { export class Blackjack extends React.Component<Record<string, never>, State> {
deck: Deck; deck: Deck;
constructor(props: Props) { constructor() {
super(props); super({});
this.deck = new Deck(DECK_COUNT); this.deck = new Deck(DECK_COUNT);
@ -64,20 +60,19 @@ export class Blackjack extends Game<Props, State> {
} }
canStartGame = (): boolean => { canStartGame = (): boolean => {
const { p } = this.props;
const { bet } = this.state; const { bet } = this.state;
return p.canAfford(bet); return Player.canAfford(bet);
}; };
startGame = (): void => { startGame = (): void => {
if (!this.canStartGame() || reachedLimit(this.props.p)) { if (!this.canStartGame() || reachedLimit()) {
return; return;
} }
// Take money from player right away so that player's dont just "leave" to avoid the loss (I mean they could // Take money from player right away so that player's dont just "leave" to avoid the loss (I mean they could
// always reload without saving but w.e) TODO: Save/Restore the RNG state to limit the value of save-scumming. // always reload without saving but w.e) TODO: Save/Restore the RNG state to limit the value of save-scumming.
this.props.p.loseMoney(this.state.bet, "casino"); win(-this.state.bet);
const playerHand = new Hand([this.deck.safeDrawCard(), this.deck.safeDrawCard()]); const playerHand = new Hand([this.deck.safeDrawCard(), this.deck.safeDrawCard()]);
const dealerHand = new Hand([this.deck.safeDrawCard(), this.deck.safeDrawCard()]); const dealerHand = new Hand([this.deck.safeDrawCard(), this.deck.safeDrawCard()]);
@ -230,7 +225,7 @@ export class Blackjack extends Game<Props, State> {
: (() => { : (() => {
throw new Error(`Unexpected result: ${result}`); throw new Error(`Unexpected result: ${result}`);
})(); // This can't happen, right? })(); // This can't happen, right?
this.win(this.props.p, gains); win(gains);
this.setState({ this.setState({
gameInProgress: false, gameInProgress: false,
result, result,
@ -239,7 +234,6 @@ export class Blackjack extends Game<Props, State> {
}; };
wagerOnChange = (event: React.ChangeEvent<HTMLInputElement>): void => { wagerOnChange = (event: React.ChangeEvent<HTMLInputElement>): void => {
const { p } = this.props;
const betInput = event.target.value; const betInput = event.target.value;
const wager = Math.round(parseFloat(betInput)); const wager = Math.round(parseFloat(betInput));
if (isNaN(wager)) { if (isNaN(wager)) {
@ -263,7 +257,7 @@ export class Blackjack extends Game<Props, State> {
wagerInvalid: true, wagerInvalid: true,
wagerInvalidHelperText: "Exceeds max bet", wagerInvalidHelperText: "Exceeds max bet",
}); });
} else if (!p.canAfford(wager)) { } else if (!Player.canAfford(wager)) {
this.setState({ this.setState({
bet: 0, bet: 0,
betInput, betInput,

@ -5,7 +5,6 @@
*/ */
import React, { useState } from "react"; import React, { useState } from "react";
import { IPlayer } from "../PersonObjects/IPlayer";
import { BadRNG } from "./RNG"; import { BadRNG } from "./RNG";
import { win, reachedLimit } from "./Game"; import { win, reachedLimit } from "./Game";
import { trusted } from "./utils"; import { trusted } from "./utils";
@ -15,14 +14,10 @@ import TextField from "@mui/material/TextField";
import Button from "@mui/material/Button"; import Button from "@mui/material/Button";
import Box from "@mui/material/Box"; import Box from "@mui/material/Box";
type IProps = {
p: IPlayer;
};
const minPlay = 0; const minPlay = 0;
const maxPlay = 10e3; const maxPlay = 10e3;
export function CoinFlip(props: IProps): React.ReactElement { export function CoinFlip(): React.ReactElement {
const [investment, setInvestment] = useState(1000); const [investment, setInvestment] = useState(1000);
const [result, setResult] = useState(<span> </span>); const [result, setResult] = useState(<span> </span>);
const [status, setStatus] = useState(""); const [status, setStatus] = useState("");
@ -43,7 +38,7 @@ export function CoinFlip(props: IProps): React.ReactElement {
} }
function play(guess: string): void { function play(guess: string): void {
if (reachedLimit(props.p)) return; if (reachedLimit()) return;
const v = BadRNG.random(); const v = BadRNG.random();
let letter: string; let letter: string;
if (v < 0.5) { if (v < 0.5) {
@ -65,11 +60,11 @@ export function CoinFlip(props: IProps): React.ReactElement {
setTimeout(() => setPlayLock(false), 250); setTimeout(() => setPlayLock(false), 250);
if (correct) { if (correct) {
win(props.p, investment); win(investment);
} else { } else {
win(props.p, -investment); win(-investment);
} }
if (reachedLimit(props.p)) return; if (reachedLimit()) return;
} }
return ( return (

16
src/Casino/Game.ts Normal file

@ -0,0 +1,16 @@
import { Player } from "../Player";
import { dialogBoxCreate } from "../ui/React/DialogBox";
const gainLimit = 10e9;
export function win(n: number): void {
Player.gainMoney(n, "casino");
}
export function reachedLimit(): boolean {
const reached = Player.getCasinoWinnings() > gainLimit;
if (reached) {
dialogBoxCreate("Alright cheater get out of here. You're not allowed here anymore.");
}
return reached;
}

@ -1,31 +0,0 @@
import * as React from "react";
import { IPlayer } from "../PersonObjects/IPlayer";
import { dialogBoxCreate } from "../ui/React/DialogBox";
const gainLimit = 10e9;
export function win(p: IPlayer, n: number): void {
p.gainMoney(n, "casino");
}
export function reachedLimit(p: IPlayer): boolean {
const reached = p.getCasinoWinnings() > gainLimit;
if (reached) {
dialogBoxCreate(<>Alright cheater get out of here. You're not allowed here anymore.</>);
}
return reached;
}
export class Game<T, U> extends React.Component<T, U> {
win(p: IPlayer, n: number): void {
p.gainMoney(n, "casino");
}
reachedLimit(p: IPlayer): boolean {
const reached = p.getCasinoWinnings() > gainLimit;
if (reached) {
dialogBoxCreate(<>Alright cheater get out of here. You're not allowed here anymore.</>);
}
return reached;
}
}

@ -1,6 +1,5 @@
import React, { useState, useEffect } from "react"; import React, { useState, useEffect } from "react";
import { IPlayer } from "../PersonObjects/IPlayer";
import { Money } from "../ui/React/Money"; import { Money } from "../ui/React/Money";
import { win, reachedLimit } from "./Game"; import { win, reachedLimit } from "./Game";
import { WHRNG } from "./RNG"; import { WHRNG } from "./RNG";
@ -9,10 +8,6 @@ import Typography from "@mui/material/Typography";
import Button from "@mui/material/Button"; import Button from "@mui/material/Button";
import TextField from "@mui/material/TextField"; import TextField from "@mui/material/TextField";
type IProps = {
p: IPlayer;
};
const minPlay = 0; const minPlay = 0;
const maxPlay = 1e7; const maxPlay = 1e7;
@ -111,7 +106,7 @@ function Single(s: number): Strategy {
}; };
} }
export function Roulette(props: IProps): React.ReactElement { export function Roulette(): React.ReactElement {
const [rng] = useState(new WHRNG(new Date().getTime())); const [rng] = useState(new WHRNG(new Date().getTime()));
const [investment, setInvestment] = useState(1000); const [investment, setInvestment] = useState(1000);
const [canPlay, setCanPlay] = useState(true); const [canPlay, setCanPlay] = useState(true);
@ -151,7 +146,7 @@ export function Roulette(props: IProps): React.ReactElement {
} }
function play(strategy: Strategy): void { function play(strategy: Strategy): void {
if (reachedLimit(props.p)) return; if (reachedLimit()) return;
setCanPlay(false); setCanPlay(false);
setLock(false); setLock(false);
@ -184,14 +179,14 @@ export function Roulette(props: IProps): React.ReactElement {
</> </>
); );
} }
win(props.p, gain); win(gain);
setCanPlay(true); setCanPlay(true);
setLock(true); setLock(true);
setStatus(status); setStatus(status);
setN(n); setN(n);
reachedLimit(props.p); reachedLimit();
}, 1600); }, 1600);
} }

@ -1,6 +1,6 @@
import React, { useState, useEffect } from "react"; import React, { useState, useEffect } from "react";
import { IPlayer } from "../PersonObjects/IPlayer"; import { Player } from "../Player";
import { Money } from "../ui/React/Money"; import { Money } from "../ui/React/Money";
import { WHRNG } from "./RNG"; import { WHRNG } from "./RNG";
import { win, reachedLimit } from "./Game"; import { win, reachedLimit } from "./Game";
@ -9,10 +9,6 @@ import Typography from "@mui/material/Typography";
import TextField from "@mui/material/TextField"; import TextField from "@mui/material/TextField";
import Button from "@mui/material/Button"; import Button from "@mui/material/Button";
type IProps = {
p: IPlayer;
};
// statically shuffled array of symbols. // statically shuffled array of symbols.
const symbols = [ const symbols = [
"D", "D",
@ -141,8 +137,8 @@ const payLines = [
const minPlay = 0; const minPlay = 0;
const maxPlay = 1e6; const maxPlay = 1e6;
export function SlotMachine(props: IProps): React.ReactElement { export function SlotMachine(): React.ReactElement {
const [rng] = useState(new WHRNG(props.p.totalPlaytime)); const [rng] = useState(new WHRNG(Player.totalPlaytime));
const [index, setIndex] = useState<number[]>([0, 0, 0, 0, 0]); const [index, setIndex] = useState<number[]>([0, 0, 0, 0, 0]);
const [locks, setLocks] = useState<number[]>([0, 0, 0, 0, 0]); const [locks, setLocks] = useState<number[]>([0, 0, 0, 0, 0]);
const [investment, setInvestment] = useState(1000); const [investment, setInvestment] = useState(1000);
@ -191,9 +187,9 @@ export function SlotMachine(props: IProps): React.ReactElement {
} }
function play(): void { function play(): void {
if (reachedLimit(props.p)) return; if (reachedLimit()) return;
setStatus("playing"); setStatus("playing");
win(props.p, -investment); win(-investment);
if (!canPlay) return; if (!canPlay) return;
unlock(); unlock();
setTimeout(lock, rng.random() * 2000 + 1000); setTimeout(lock, rng.random() * 2000 + 1000);
@ -235,7 +231,7 @@ export function SlotMachine(props: IProps): React.ReactElement {
if (count < 3) continue; if (count < 3) continue;
const payout = getPayout(data[0], count - 3); const payout = getPayout(data[0], count - 3);
gains += investment * payout; gains += investment * payout;
win(props.p, investment * payout); win(investment * payout);
} }
setStatus( setStatus(
@ -244,7 +240,7 @@ export function SlotMachine(props: IProps): React.ReactElement {
</>, </>,
); );
setCanPlay(true); setCanPlay(true);
if (reachedLimit(props.p)) return; if (reachedLimit()) return;
} }
function unlock(): void { function unlock(): void {

@ -191,7 +191,8 @@ function getRandomFilename(server: BaseServer, reward: ICodingContractReward): s
} }
if (reward.name) { if (reward.name) {
contractFn += `-${reward.name.replace(/\s/g, "")}`; // Only alphanumeric characters in the reward name.
contractFn += `-${reward.name.replace(/[^a-zA-Z0-9]/g, "")}`;
} }
return contractFn; return contractFn;

@ -29,7 +29,7 @@ export function initCompanies(): void {
for (const companyName of Object.keys(Companies)) { for (const companyName of Object.keys(Companies)) {
const company = Companies[companyName]; const company = Companies[companyName];
const oldCompany = oldCompanies[companyName]; const oldCompany = oldCompanies[companyName];
if (!(oldCompany instanceof Company)) { if (!oldCompany) {
// New game, so no OldCompanies data // New game, so no OldCompanies data
company.favor = 0; company.favor = 0;
} else { } else {

@ -88,11 +88,7 @@ export class Company {
} }
hasPosition(pos: CompanyPosition | string): boolean { hasPosition(pos: CompanyPosition | string): boolean {
if (pos instanceof CompanyPosition) { return this.companyPositions[typeof pos === "string" ? pos : pos.name] != null;
return this.companyPositions[pos.name] != null;
} else {
return this.companyPositions[pos] != null;
}
} }
hasAgentPositions(): boolean { hasAgentPositions(): boolean {

@ -1,6 +1,6 @@
import React from "react"; import React from "react";
import { Company } from "../Company"; import { Company } from "../Company";
import { use } from "../../ui/Context"; import { Player } from "../../Player";
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";
@ -14,9 +14,8 @@ interface IProps {
} }
export function QuitJobModal(props: IProps): React.ReactElement { export function QuitJobModal(props: IProps): React.ReactElement {
const player = use.Player();
function quit(): void { function quit(): void {
player.quitJob(props.locName); Player.quitJob(props.locName);
props.onQuit(); props.onQuit();
props.onClose(); props.onClose();
} }

@ -1,9 +1,6 @@
import { Player } from "../Player"; import { Player } from "../Player";
import { IPlayer } from "src/PersonObjects/IPlayer";
import { MaterialSizes } from "./MaterialSizes"; import { MaterialSizes } from "./MaterialSizes";
import { ICorporation } from "./ICorporation";
import { Corporation } from "./Corporation"; import { Corporation } from "./Corporation";
import { IIndustry } from "./IIndustry";
import { IndustryStartingCosts, IndustryResearchTrees } from "./IndustryData"; import { IndustryStartingCosts, IndustryResearchTrees } from "./IndustryData";
import { Industry } from "./Industry"; import { Industry } from "./Industry";
import { CorporationConstants } from "./data/Constants"; import { CorporationConstants } from "./data/Constants";
@ -18,7 +15,7 @@ import { EmployeePositions } from "./EmployeePositions";
import { ResearchMap } from "./ResearchMap"; import { ResearchMap } from "./ResearchMap";
import { isRelevantMaterial } from "./ui/Helpers"; import { isRelevantMaterial } from "./ui/Helpers";
export function NewIndustry(corporation: ICorporation, industry: string, name: string): void { export function NewIndustry(corporation: Corporation, industry: string, name: string): void {
if (corporation.divisions.find(({ type }) => industry == type)) if (corporation.divisions.find(({ type }) => industry == type))
throw new Error(`You have already expanded into the ${industry} industry!`); throw new Error(`You have already expanded into the ${industry} industry!`);
@ -48,7 +45,7 @@ export function NewIndustry(corporation: ICorporation, industry: string, name: s
} }
} }
export function NewCity(corporation: ICorporation, division: IIndustry, city: string): void { export function NewCity(corporation: Corporation, division: Industry, city: string): void {
if (corporation.funds < CorporationConstants.OfficeInitialCost) { if (corporation.funds < CorporationConstants.OfficeInitialCost) {
throw new Error("You don't have enough company funds to open a new office!"); throw new Error("You don't have enough company funds to open a new office!");
} }
@ -62,7 +59,7 @@ export function NewCity(corporation: ICorporation, division: IIndustry, city: st
}); });
} }
export function UnlockUpgrade(corporation: ICorporation, upgrade: CorporationUnlockUpgrade): void { export function UnlockUpgrade(corporation: Corporation, upgrade: CorporationUnlockUpgrade): void {
if (corporation.funds < upgrade.price) { if (corporation.funds < upgrade.price) {
throw new Error("Insufficient funds"); throw new Error("Insufficient funds");
} }
@ -72,7 +69,7 @@ export function UnlockUpgrade(corporation: ICorporation, upgrade: CorporationUnl
corporation.unlock(upgrade); corporation.unlock(upgrade);
} }
export function LevelUpgrade(corporation: ICorporation, upgrade: CorporationUpgrade): void { export function LevelUpgrade(corporation: Corporation, upgrade: CorporationUpgrade): void {
const baseCost = upgrade.basePrice; const baseCost = upgrade.basePrice;
const priceMult = upgrade.priceMult; const priceMult = upgrade.priceMult;
const level = corporation.upgrades[upgrade.index]; const level = corporation.upgrades[upgrade.index];
@ -84,7 +81,7 @@ export function LevelUpgrade(corporation: ICorporation, upgrade: CorporationUpgr
} }
} }
export function IssueDividends(corporation: ICorporation, rate: number): void { export function IssueDividends(corporation: Corporation, rate: number): void {
if (isNaN(rate) || rate < 0 || rate > CorporationConstants.DividendMaxRate) { if (isNaN(rate) || rate < 0 || rate > CorporationConstants.DividendMaxRate) {
throw new Error(`Invalid value. Must be an number between 0 and ${CorporationConstants.DividendMaxRate}`); throw new Error(`Invalid value. Must be an number between 0 and ${CorporationConstants.DividendMaxRate}`);
} }
@ -253,7 +250,7 @@ export function BuyMaterial(material: Material, amt: number): void {
material.buy = amt; material.buy = amt;
} }
export function BulkPurchase(corp: ICorporation, warehouse: Warehouse, material: Material, amt: number): void { export function BulkPurchase(corp: Corporation, warehouse: Warehouse, material: Material, amt: number): void {
const matSize = MaterialSizes[material.name]; const matSize = MaterialSizes[material.name];
const maxAmount = (warehouse.size - warehouse.sizeUsed) / matSize; const maxAmount = (warehouse.size - warehouse.sizeUsed) / matSize;
if (isNaN(amt) || amt < 0) { if (isNaN(amt) || amt < 0) {
@ -271,7 +268,7 @@ export function BulkPurchase(corp: ICorporation, warehouse: Warehouse, material:
} }
} }
export function SellShares(corporation: ICorporation, player: IPlayer, numShares: number): number { export function SellShares(corporation: Corporation, numShares: number): number {
if (isNaN(numShares)) throw new Error("Invalid value for number of shares"); if (isNaN(numShares)) throw new Error("Invalid value for number of shares");
if (numShares < 0) throw new Error("Invalid value for number of shares"); if (numShares < 0) throw new Error("Invalid value for number of shares");
if (numShares > corporation.numShares) throw new Error("You don't have that many shares to sell!"); if (numShares > corporation.numShares) throw new Error("You don't have that many shares to sell!");
@ -287,20 +284,20 @@ export function SellShares(corporation: ICorporation, player: IPlayer, numShares
corporation.sharePrice = newSharePrice; corporation.sharePrice = newSharePrice;
corporation.shareSalesUntilPriceUpdate = newSharesUntilUpdate; corporation.shareSalesUntilPriceUpdate = newSharesUntilUpdate;
corporation.shareSaleCooldown = CorporationConstants.SellSharesCooldown; corporation.shareSaleCooldown = CorporationConstants.SellSharesCooldown;
player.gainMoney(profit, "corporation"); Player.gainMoney(profit, "corporation");
return profit; return profit;
} }
export function BuyBackShares(corporation: ICorporation, player: IPlayer, numShares: number): boolean { export function BuyBackShares(corporation: Corporation, numShares: number): boolean {
if (isNaN(numShares)) throw new Error("Invalid value for number of shares"); if (isNaN(numShares)) throw new Error("Invalid value for number of shares");
if (numShares < 0) throw new Error("Invalid value for number of shares"); if (numShares < 0) throw new Error("Invalid value for number of shares");
if (numShares > corporation.issuedShares) throw new Error("You don't have that many shares to buy!"); if (numShares > corporation.issuedShares) throw new Error("You don't have that many shares to buy!");
if (!corporation.public) throw new Error("You haven't gone public!"); if (!corporation.public) throw new Error("You haven't gone public!");
const buybackPrice = corporation.sharePrice * 1.1; const buybackPrice = corporation.sharePrice * 1.1;
if (player.money < numShares * buybackPrice) throw new Error("You cant afford that many shares!"); if (Player.money < numShares * buybackPrice) throw new Error("You cant afford that many shares!");
corporation.numShares += numShares; corporation.numShares += numShares;
corporation.issuedShares -= numShares; corporation.issuedShares -= numShares;
player.loseMoney(numShares * buybackPrice, "corporation"); Player.loseMoney(numShares * buybackPrice, "corporation");
return true; return true;
} }
@ -319,7 +316,7 @@ export function AutoAssignJob(office: OfficeSpace, job: string, count: number):
return office.autoAssignJob(job, count); return office.autoAssignJob(job, count);
} }
export function UpgradeOfficeSize(corp: ICorporation, office: OfficeSpace, size: number): void { export function UpgradeOfficeSize(corp: Corporation, office: OfficeSpace, size: number): void {
const initialPriceMult = Math.round(office.size / CorporationConstants.OfficeInitialSize); const initialPriceMult = Math.round(office.size / CorporationConstants.OfficeInitialSize);
const costMultiplier = 1.09; const costMultiplier = 1.09;
// Calculate cost to upgrade size by 15 employees // Calculate cost to upgrade size by 15 employees
@ -333,7 +330,7 @@ export function UpgradeOfficeSize(corp: ICorporation, office: OfficeSpace, size:
corp.funds = corp.funds - cost; corp.funds = corp.funds - cost;
} }
export function BuyCoffee(corp: ICorporation, office: OfficeSpace): boolean { export function BuyCoffee(corp: Corporation, office: OfficeSpace): boolean {
const cost = office.getCoffeeCost(); const cost = office.getCoffeeCost();
if (corp.funds < cost) { if (corp.funds < cost) {
return false; return false;
@ -347,7 +344,7 @@ export function BuyCoffee(corp: ICorporation, office: OfficeSpace): boolean {
return true; return true;
} }
export function ThrowParty(corp: ICorporation, office: OfficeSpace, costPerEmployee: number): number { export function ThrowParty(corp: Corporation, office: OfficeSpace, costPerEmployee: number): number {
const mult = 1 + costPerEmployee / 10e6; const mult = 1 + costPerEmployee / 10e6;
const cost = costPerEmployee * office.employees.length; const cost = costPerEmployee * office.employees.length;
if (corp.funds < cost) { if (corp.funds < cost) {
@ -362,9 +359,9 @@ export function ThrowParty(corp: ICorporation, office: OfficeSpace, costPerEmplo
return mult; return mult;
} }
export function PurchaseWarehouse(corp: ICorporation, division: IIndustry, city: string): void { export function PurchaseWarehouse(corp: Corporation, division: Industry, city: string): void {
if (corp.funds < CorporationConstants.WarehouseInitialCost) return; if (corp.funds < CorporationConstants.WarehouseInitialCost) return;
if (division.warehouses[city] instanceof Warehouse) return; if (division.warehouses[city]) return;
division.warehouses[city] = new Warehouse({ division.warehouses[city] = new Warehouse({
corp: corp, corp: corp,
industry: division, industry: division,
@ -381,7 +378,7 @@ export function UpgradeWarehouseCost(warehouse: Warehouse, amt: number): number
); );
} }
export function UpgradeWarehouse(corp: ICorporation, division: IIndustry, warehouse: Warehouse, amt = 1): void { export function UpgradeWarehouse(corp: Corporation, division: Industry, warehouse: Warehouse, amt = 1): void {
const sizeUpgradeCost = UpgradeWarehouseCost(warehouse, amt); const sizeUpgradeCost = UpgradeWarehouseCost(warehouse, amt);
if (corp.funds < sizeUpgradeCost) return; if (corp.funds < sizeUpgradeCost) return;
warehouse.level += amt; warehouse.level += amt;
@ -389,7 +386,7 @@ export function UpgradeWarehouse(corp: ICorporation, division: IIndustry, wareho
corp.funds = corp.funds - sizeUpgradeCost; corp.funds = corp.funds - sizeUpgradeCost;
} }
export function HireAdVert(corp: ICorporation, division: IIndustry): void { export function HireAdVert(corp: Corporation, division: Industry): void {
const cost = division.getAdVertCost(); const cost = division.getAdVertCost();
if (corp.funds < cost) return; if (corp.funds < cost) return;
corp.funds = corp.funds - cost; corp.funds = corp.funds - cost;
@ -397,8 +394,8 @@ export function HireAdVert(corp: ICorporation, division: IIndustry): void {
} }
export function MakeProduct( export function MakeProduct(
corp: ICorporation, corp: Corporation,
division: IIndustry, division: Industry,
city: string, city: string,
productName: string, productName: string,
designInvest: number, designInvest: number,
@ -442,7 +439,7 @@ export function MakeProduct(
designCost: designInvest, designCost: designInvest,
advCost: marketingInvest, advCost: marketingInvest,
}); });
if (products[product.name] instanceof Product) { if (products[product.name]) {
throw new Error(`You already have a product with this name!`); throw new Error(`You already have a product with this name!`);
} }
@ -450,7 +447,7 @@ export function MakeProduct(
products[product.name] = product; products[product.name] = product;
} }
export function Research(division: IIndustry, researchName: string): void { export function Research(division: Industry, researchName: string): void {
const researchTree = IndustryResearchTrees[division.type]; const researchTree = IndustryResearchTrees[division.type];
if (researchTree === undefined) throw new Error(`No research tree for industry '${division.type}'`); if (researchTree === undefined) throw new Error(`No research tree for industry '${division.type}'`);
const allResearch = researchTree.getAllNodes(); const allResearch = researchTree.getAllNodes();
@ -473,10 +470,10 @@ export function Research(division: IIndustry, researchName: string): void {
for (let i = 0; i < CorporationConstants.Cities.length; ++i) { for (let i = 0; i < CorporationConstants.Cities.length; ++i) {
const city = CorporationConstants.Cities[i]; const city = CorporationConstants.Cities[i];
const warehouse = division.warehouses[city]; const warehouse = division.warehouses[city];
if (!(warehouse instanceof Warehouse)) { if (!warehouse) {
continue; continue;
} }
if (Player.corporation instanceof Corporation) { if (Player.corporation) {
// Stores cycles in a "buffer". Processed separately using Engine Counters // Stores cycles in a "buffer". Processed separately using Engine Counters
warehouse.updateSize(Player.corporation, division); warehouse.updateSize(Player.corporation, division);
} }

@ -1,14 +1,13 @@
import { CorporationState } from "./CorporationState"; import { CorporationState } from "./CorporationState";
import { CorporationUnlockUpgrade, CorporationUnlockUpgrades } from "./data/CorporationUnlockUpgrades"; import { CorporationUnlockUpgrade, CorporationUnlockUpgrades } from "./data/CorporationUnlockUpgrades";
import { CorporationUpgrade, CorporationUpgrades } from "./data/CorporationUpgrades"; import { CorporationUpgrade, CorporationUpgrades } from "./data/CorporationUpgrades";
import { Warehouse } from "./Warehouse";
import { CorporationConstants } from "./data/Constants"; import { CorporationConstants } from "./data/Constants";
import { Industry } from "./Industry"; import { Industry } from "./Industry";
import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers"; import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers";
import { showLiterature } from "../Literature/LiteratureHelpers"; import { showLiterature } from "../Literature/LiteratureHelpers";
import { LiteratureNames } from "../Literature/data/LiteratureNames"; import { LiteratureNames } from "../Literature/data/LiteratureNames";
import { IPlayer } from "../PersonObjects/IPlayer"; import { Player } from "../Player";
import { dialogBoxCreate } from "../ui/React/DialogBox"; import { dialogBoxCreate } from "../ui/React/DialogBox";
import { Reviver, Generic_toJSON, Generic_fromJSON, IReviverValue } from "../utils/JSONReviver"; import { Reviver, Generic_toJSON, Generic_fromJSON, IReviverValue } from "../utils/JSONReviver";
@ -76,7 +75,7 @@ export class Corporation {
this.storedCycles += numCycles; this.storedCycles += numCycles;
} }
process(player: IPlayer): void { process(): void {
if (this.storedCycles >= CorporationConstants.CyclesPerIndustryStateCycle) { if (this.storedCycles >= CorporationConstants.CyclesPerIndustryStateCycle) {
const state = this.getState(); const state = this.getState();
const marketCycles = 1; const marketCycles = 1;
@ -120,7 +119,7 @@ export class Corporation {
if (isNaN(this.funds) || this.funds === Infinity || this.funds === -Infinity) { if (isNaN(this.funds) || this.funds === Infinity || this.funds === -Infinity) {
dialogBoxCreate( dialogBoxCreate(
"There was an error calculating your Corporations funds and they got reset to 0. " + "There was an error calculating your Corporations funds and they got reset to 0. " +
"This is a bug. Please report to game developer.<br><br>" + "This is a bug. Please report to game developer.\n\n" +
"(Your funds have been set to $150b for the inconvenience)", "(Your funds have been set to $150b for the inconvenience)",
); );
this.funds = 150e9; this.funds = 150e9;
@ -139,7 +138,7 @@ export class Corporation {
} else { } else {
const totalDividends = this.dividendRate * cycleProfit; const totalDividends = this.dividendRate * cycleProfit;
const retainedEarnings = cycleProfit - totalDividends; const retainedEarnings = cycleProfit - totalDividends;
player.gainMoney(this.getCycleDividends(), "corporation"); Player.gainMoney(this.getCycleDividends(), "corporation");
this.addFunds(retainedEarnings); this.addFunds(retainedEarnings);
} }
} else { } else {
@ -331,7 +330,7 @@ export class Corporation {
for (const city of Object.keys(industry.warehouses)) { for (const city of Object.keys(industry.warehouses)) {
const warehouse = industry.warehouses[city]; const warehouse = industry.warehouses[city];
if (warehouse === 0) continue; if (warehouse === 0) continue;
if (industry.warehouses.hasOwnProperty(city) && warehouse instanceof Warehouse) { if (industry.warehouses.hasOwnProperty(city) && warehouse) {
warehouse.updateSize(this, industry); warehouse.updateSize(this, industry);
} }
} }
@ -428,9 +427,9 @@ export class Corporation {
// Adds the Corporation Handbook (Starter Guide) to the player's home computer. // Adds the Corporation Handbook (Starter Guide) to the player's home computer.
// This is a lit file that gives introductory info to the player // This is a lit file that gives introductory info to the player
// This occurs when the player clicks the "Getting Started Guide" button on the overview panel // This occurs when the player clicks the "Getting Started Guide" button on the overview panel
getStarterGuide(player: IPlayer): void { getStarterGuide(): void {
// Check if player already has Corporation Handbook // Check if player already has Corporation Handbook
const homeComp = player.getHomeComputer(); const homeComp = Player.getHomeComputer();
let hasHandbook = false; let hasHandbook = false;
const handbookFn = LiteratureNames.CorporationManagementHandbook; const handbookFn = LiteratureNames.CorporationManagementHandbook;
for (let i = 0; i < homeComp.messages.length; ++i) { for (let i = 0; i < homeComp.messages.length; ++i) {

@ -2,8 +2,8 @@ import { CorporationConstants } from "./data/Constants";
import { getRandomInt } from "../utils/helpers/getRandomInt"; import { getRandomInt } from "../utils/helpers/getRandomInt";
import { Generic_fromJSON, Generic_toJSON, IReviverValue, Reviver } from "../utils/JSONReviver"; import { Generic_fromJSON, Generic_toJSON, IReviverValue, Reviver } from "../utils/JSONReviver";
import { EmployeePositions } from "./EmployeePositions"; import { EmployeePositions } from "./EmployeePositions";
import { ICorporation } from "./ICorporation"; import { Corporation } from "./Corporation";
import { IIndustry } from "./IIndustry"; import { Industry } from "./Industry";
interface IParams { interface IParams {
name?: string; name?: string;
@ -77,7 +77,7 @@ export class Employee {
return salary; return salary;
} }
calculateProductivity(corporation: ICorporation, industry: IIndustry): number { calculateProductivity(corporation: Corporation, industry: Industry): number {
const effCre = this.cre * corporation.getEmployeeCreMultiplier() * industry.getEmployeeCreMultiplier(), const effCre = this.cre * corporation.getEmployeeCreMultiplier() * industry.getEmployeeCreMultiplier(),
effCha = this.cha * corporation.getEmployeeChaMultiplier() * industry.getEmployeeChaMultiplier(), effCha = this.cha * corporation.getEmployeeChaMultiplier() * industry.getEmployeeChaMultiplier(),
effInt = this.int * corporation.getEmployeeIntMultiplier() * industry.getEmployeeIntMultiplier(), effInt = this.int * corporation.getEmployeeIntMultiplier() * industry.getEmployeeIntMultiplier(),

@ -1,63 +0,0 @@
import { Industry } from "./Industry";
import { IPlayer } from "../PersonObjects/IPlayer";
import { CorporationUnlockUpgrade } from "./data/CorporationUnlockUpgrades";
import { CorporationUpgrade } from "./data/CorporationUpgrades";
import { CorporationState } from "./CorporationState";
import { IReviverValue } from "../utils/JSONReviver";
export interface ICorporation {
name: string;
divisions: Industry[];
funds: number;
revenue: number;
expenses: number;
fundingRound: number;
public: boolean;
totalShares: number;
numShares: number;
shareSalesUntilPriceUpdate: number;
shareSaleCooldown: number;
issueNewSharesCooldown: number;
dividendRate: number;
dividendTax: number;
issuedShares: number;
sharePrice: number;
storedCycles: number;
valuation: number;
unlockUpgrades: number[];
upgrades: number[];
upgradeMultipliers: number[];
state: CorporationState;
addFunds(amt: number): void;
getState(): string;
storeCycles(numCycles: number): void;
process(player: IPlayer): void;
determineValuation(): void;
determineCycleValuation(): number;
getTargetSharePrice(): number;
updateSharePrice(): void;
immediatelyUpdateSharePrice(): void;
calculateShareSale(numShares: number): [number, number, number];
convertCooldownToString(cd: number): string;
unlock(upgrade: CorporationUnlockUpgrade): void;
upgrade(upgrade: CorporationUpgrade): void;
getProductionMultiplier(): number;
getStorageMultiplier(): number;
getDreamSenseGain(): number;
getAdvertisingMultiplier(): number;
getEmployeeCreMultiplier(): number;
getEmployeeChaMultiplier(): number;
getEmployeeIntMultiplier(): number;
getEmployeeEffMultiplier(): number;
getSalesMultiplier(): number;
getScientificResearchMultiplier(): number;
getStarterGuide(player: IPlayer): void;
updateDividendTax(): void;
getCycleDividends(): number;
toJSON(): IReviverValue;
}

@ -1,78 +0,0 @@
import { Material } from "./Material";
import { Warehouse } from "./Warehouse";
import { ICorporation } from "./ICorporation";
import { OfficeSpace } from "./OfficeSpace";
import { Product } from "./Product";
import { IReviverValue } from "../utils/JSONReviver";
export interface IIndustry {
name: string;
type: string;
sciResearch: Material;
researched: { [key: string]: boolean | undefined };
reqMats: { [key: string]: number | undefined };
prodMats: string[];
products: { [key: string]: Product | undefined };
makesProducts: boolean;
awareness: number;
popularity: number;
startingCost: number;
reFac: number;
sciFac: number;
hwFac: number;
robFac: number;
aiFac: number;
advFac: number;
prodMult: number;
// Decimal
lastCycleRevenue: number;
lastCycleExpenses: number;
thisCycleRevenue: number;
thisCycleExpenses: number;
state: string;
newInd: boolean;
warehouses: { [key: string]: Warehouse | 0 };
offices: { [key: string]: OfficeSpace | 0 };
numAdVerts: number;
init(): void;
getProductDescriptionText(): string;
getMaximumNumberProducts(): number;
hasMaximumNumberProducts(): boolean;
calculateProductionFactors(): void;
updateWarehouseSizeUsed(warehouse: Warehouse): void;
process(marketCycles: number, state: string, corporation: ICorporation): void;
processMaterialMarket(): void;
processProductMarket(marketCycles: number): void;
processMaterials(marketCycles: number, corporation: ICorporation): [number, number];
processProducts(marketCycles: number, corporation: ICorporation): [number, number];
processProduct(marketCycles: number, product: Product, corporation: ICorporation): number;
resetImports(state: string): void;
discontinueProduct(product: Product): void;
getAdVertCost(): number;
applyAdVert(corporation: ICorporation): void;
getOfficeProductivity(office: OfficeSpace, params?: { forProduct?: boolean }): number;
getBusinessFactor(office: OfficeSpace): number;
getAdvertisingFactors(): [number, number, number, number];
getMarketFactor(mat: { dmd: number; cmp: number }): number;
hasResearch(name: string): boolean;
updateResearchTree(): void;
getAdvertisingMultiplier(): number;
getEmployeeChaMultiplier(): number;
getEmployeeCreMultiplier(): number;
getEmployeeEffMultiplier(): number;
getEmployeeIntMultiplier(): number;
getProductionMultiplier(): number;
getProductProductionMultiplier(): number;
getSalesMultiplier(): number;
getScientificResearchMultiplier(): number;
getStorageMultiplier(): number;
toJSON(): IReviverValue;
}

@ -12,16 +12,15 @@ import { dialogBoxCreate } from "../ui/React/DialogBox";
import { isString } from "../utils/helpers/isString"; import { isString } from "../utils/helpers/isString";
import { MaterialSizes } from "./MaterialSizes"; import { MaterialSizes } from "./MaterialSizes";
import { Warehouse } from "./Warehouse"; import { Warehouse } from "./Warehouse";
import { ICorporation } from "./ICorporation"; import { Corporation } from "./Corporation";
import { IIndustry } from "./IIndustry";
interface IParams { interface IParams {
name?: string; name?: string;
corp?: ICorporation; corp?: Corporation;
type?: string; type?: string;
} }
export class Industry implements IIndustry { export class Industry {
name = ""; name = "";
type = Industries.Agriculture; type = Industries.Agriculture;
sciResearch = new Material({ name: "Scientific Research" }); sciResearch = new Material({ name: "Scientific Research" });
@ -356,9 +355,7 @@ export class Industry implements IIndustry {
for (let i = 0; i < CorporationConstants.Cities.length; ++i) { for (let i = 0; i < CorporationConstants.Cities.length; ++i) {
const city = CorporationConstants.Cities[i]; const city = CorporationConstants.Cities[i];
const warehouse = this.warehouses[city]; const warehouse = this.warehouses[city];
if (!(warehouse instanceof Warehouse)) { if (!warehouse) continue;
continue;
}
const materials = warehouse.materials; const materials = warehouse.materials;
@ -385,7 +382,7 @@ export class Industry implements IIndustry {
} }
} }
process(marketCycles = 1, state: string, corporation: ICorporation): void { process(marketCycles = 1, state: string, corporation: Corporation): void {
this.state = state; this.state = state;
//At the start of a cycle, store and reset revenue/expenses //At the start of a cycle, store and reset revenue/expenses
@ -414,10 +411,7 @@ export class Industry implements IIndustry {
let employeeSalary = 0; let employeeSalary = 0;
for (const officeLoc of Object.keys(this.offices)) { for (const officeLoc of Object.keys(this.offices)) {
const office = this.offices[officeLoc]; const office = this.offices[officeLoc];
if (office === 0) continue; if (office) employeeSalary += office.process(marketCycles, corporation, this);
if (office instanceof OfficeSpace) {
employeeSalary += office.process(marketCycles, corporation, this);
}
} }
this.thisCycleExpenses = this.thisCycleExpenses + employeeSalary; this.thisCycleExpenses = this.thisCycleExpenses + employeeSalary;
@ -468,7 +462,7 @@ export class Industry implements IIndustry {
for (let i = 0; i < CorporationConstants.Cities.length; ++i) { for (let i = 0; i < CorporationConstants.Cities.length; ++i) {
//If this industry has a warehouse in this city, process the market //If this industry has a warehouse in this city, process the market
//for every material this industry requires or produces //for every material this industry requires or produces
if (this.warehouses[CorporationConstants.Cities[i]] instanceof Warehouse) { if (this.warehouses[CorporationConstants.Cities[i]]) {
const wh = this.warehouses[CorporationConstants.Cities[i]]; const wh = this.warehouses[CorporationConstants.Cities[i]];
if (wh === 0) continue; if (wh === 0) continue;
for (const name of Object.keys(reqMats)) { for (const name of Object.keys(reqMats)) {
@ -518,7 +512,7 @@ export class Industry implements IIndustry {
} }
//Process production, purchase, and import/export of materials //Process production, purchase, and import/export of materials
processMaterials(marketCycles = 1, corporation: ICorporation): [number, number] { processMaterials(marketCycles = 1, corporation: Corporation): [number, number] {
let revenue = 0, let revenue = 0,
expenses = 0; expenses = 0;
this.calculateProductionFactors(); this.calculateProductionFactors();
@ -528,7 +522,7 @@ export class Industry implements IIndustry {
const office = this.offices[city]; const office = this.offices[city];
if (office === 0) continue; if (office === 0) continue;
if (this.warehouses[city] instanceof Warehouse) { if (this.warehouses[city]) {
const warehouse = this.warehouses[city]; const warehouse = this.warehouses[city];
if (warehouse === 0) continue; if (warehouse === 0) continue;
@ -825,14 +819,7 @@ export class Industry implements IIndustry {
sellAmt = eval(tmp); sellAmt = eval(tmp);
} catch (e) { } catch (e) {
dialogBoxCreate( dialogBoxCreate(
"Error evaluating your sell amount for material " + `Error evaluating your sell amount for material ${mat.name} in ${this.name}'s ${city} office. The sell amount is being set to zero`,
mat.name +
" in " +
this.name +
"'s " +
city +
" office. The sell amount " +
"is being set to zero",
); );
sellAmt = 0; sellAmt = 0;
} }
@ -879,27 +866,13 @@ export class Industry implements IIndustry {
amt = eval(amtStr); amt = eval(amtStr);
} catch (e) { } catch (e) {
dialogBoxCreate( dialogBoxCreate(
"Calculating export for " + `Calculating export for ${mat.name} in ${this.name}'s ${city} division failed with error: ${e}`,
mat.name +
" in " +
this.name +
"'s " +
city +
" division failed with " +
"error: " +
e,
); );
continue; continue;
} }
if (isNaN(amt)) { if (isNaN(amt)) {
dialogBoxCreate( dialogBoxCreate(
"Error calculating export amount for " + `Error calculating export amount for ${mat.name} in ${this.name}'s ${city} division.`,
mat.name +
" in " +
this.name +
"'s " +
city +
" division.",
); );
continue; continue;
} }
@ -915,7 +888,7 @@ export class Industry implements IIndustry {
if (corporation.divisions[foo].name === exp.ind) { if (corporation.divisions[foo].name === exp.ind) {
const expIndustry = corporation.divisions[foo]; const expIndustry = corporation.divisions[foo];
const expWarehouse = expIndustry.warehouses[exp.city]; const expWarehouse = expIndustry.warehouses[exp.city];
if (!(expWarehouse instanceof Warehouse)) { if (!expWarehouse) {
console.error(`Invalid export! ${expIndustry.name} ${exp.city}`); console.error(`Invalid export! ${expIndustry.name} ${exp.city}`);
break; break;
} }
@ -958,7 +931,7 @@ export class Industry implements IIndustry {
//Produce Scientific Research based on R&D employees //Produce Scientific Research based on R&D employees
//Scientific Research can be produced without a warehouse //Scientific Research can be produced without a warehouse
if (office instanceof OfficeSpace) { if (office) {
this.sciResearch.qty += this.sciResearch.qty +=
0.004 * 0.004 *
Math.pow(office.employeeProd[EmployeePositions.RandD], 0.5) * Math.pow(office.employeeProd[EmployeePositions.RandD], 0.5) *
@ -970,7 +943,7 @@ export class Industry implements IIndustry {
} }
//Process production & sale of this industry's FINISHED products (including all of their stats) //Process production & sale of this industry's FINISHED products (including all of their stats)
processProducts(marketCycles = 1, corporation: ICorporation): [number, number] { processProducts(marketCycles = 1, corporation: Corporation): [number, number] {
let revenue = 0; let revenue = 0;
const expenses = 0; const expenses = 0;
@ -997,7 +970,7 @@ export class Industry implements IIndustry {
for (const prodName of Object.keys(this.products)) { for (const prodName of Object.keys(this.products)) {
if (this.products.hasOwnProperty(prodName)) { if (this.products.hasOwnProperty(prodName)) {
const prod = this.products[prodName]; const prod = this.products[prodName];
if (prod instanceof Product && prod.fin) { if (prod && prod.fin) {
revenue += this.processProduct(marketCycles, prod, corporation); revenue += this.processProduct(marketCycles, prod, corporation);
} }
} }
@ -1006,14 +979,14 @@ export class Industry implements IIndustry {
} }
//Processes FINISHED products //Processes FINISHED products
processProduct(marketCycles = 1, product: Product, corporation: ICorporation): number { processProduct(marketCycles = 1, product: Product, corporation: Corporation): number {
let totalProfit = 0; let totalProfit = 0;
for (let i = 0; i < CorporationConstants.Cities.length; ++i) { for (let i = 0; i < CorporationConstants.Cities.length; ++i) {
const city = CorporationConstants.Cities[i]; const city = CorporationConstants.Cities[i];
const office = this.offices[city]; const office = this.offices[city];
if (office === 0) continue; if (office === 0) continue;
const warehouse = this.warehouses[city]; const warehouse = this.warehouses[city];
if (warehouse instanceof Warehouse) { if (warehouse) {
switch (this.state) { switch (this.state) {
case "PRODUCTION": { case "PRODUCTION": {
//Calculate the maximum production of this material based //Calculate the maximum production of this material based
@ -1172,13 +1145,7 @@ export class Industry implements IIndustry {
tmp = eval(tmp); tmp = eval(tmp);
} catch (e) { } catch (e) {
dialogBoxCreate( dialogBoxCreate(
"Error evaluating your sell price expression for " + `Error evaluating your sell price expression for ${product.name} in ${this.name}'s ${city} office. Sell price is being set to MAX`,
product.name +
" in " +
this.name +
"'s " +
city +
" office. Sell price is being set to MAX",
); );
tmp = product.maxsll; tmp = product.maxsll;
} }
@ -1223,7 +1190,7 @@ export class Industry implements IIndustry {
if (state === "EXPORT") { if (state === "EXPORT") {
for (let i = 0; i < CorporationConstants.Cities.length; ++i) { for (let i = 0; i < CorporationConstants.Cities.length; ++i) {
const city = CorporationConstants.Cities[i]; const city = CorporationConstants.Cities[i];
if (!(this.warehouses[city] instanceof Warehouse)) { if (!this.warehouses[city]) {
continue; continue;
} }
const warehouse = this.warehouses[city]; const warehouse = this.warehouses[city];
@ -1252,7 +1219,7 @@ export class Industry implements IIndustry {
return 1e9 * Math.pow(1.06, this.numAdVerts); return 1e9 * Math.pow(1.06, this.numAdVerts);
} }
applyAdVert(corporation: ICorporation): void { applyAdVert(corporation: Corporation): void {
const advMult = corporation.getAdvertisingMultiplier() * this.getAdvertisingMultiplier(); const advMult = corporation.getAdvertisingMultiplier() * this.getAdvertisingMultiplier();
const awareness = (this.awareness + 3 * advMult) * (1.01 * advMult); const awareness = (this.awareness + 3 * advMult) * (1.01 * advMult);
this.awareness = Math.min(awareness, Number.MAX_VALUE); this.awareness = Math.min(awareness, Number.MAX_VALUE);

@ -1,6 +1,6 @@
import React from "react"; import React from "react";
import { ResearchTree } from "./ResearchTree"; import { ResearchTree } from "./ResearchTree";
import { ICorporation } from "./ICorporation"; import { Corporation } from "./Corporation";
import { getBaseResearchTreeCopy, getProductIndustryResearchTreeCopy } from "./data/BaseResearchTree"; import { getBaseResearchTreeCopy, getProductIndustryResearchTreeCopy } from "./data/BaseResearchTree";
import { MoneyCost } from "./ui/MoneyCost"; import { MoneyCost } from "./ui/MoneyCost";
@ -59,8 +59,8 @@ export const IndustryStartingCosts: IIndustryMap<number> = {
}; };
// Map of description for each industry // Map of description for each industry
export const IndustryDescriptions: IIndustryMap<(corp: ICorporation) => React.ReactElement> = { export const IndustryDescriptions: IIndustryMap<(corp: Corporation) => React.ReactElement> = {
Energy: (corp: ICorporation) => ( Energy: (corp: Corporation) => (
<> <>
Engage in the production and distribution of energy. Engage in the production and distribution of energy.
<br /> <br />
@ -70,7 +70,7 @@ export const IndustryDescriptions: IIndustryMap<(corp: ICorporation) => React.Re
Recommended starting Industry: NO Recommended starting Industry: NO
</> </>
), ),
Utilities: (corp: ICorporation) => ( Utilities: (corp: Corporation) => (
<> <>
Distribute water and provide wastewater services. Distribute water and provide wastewater services.
<br /> <br />
@ -80,7 +80,7 @@ export const IndustryDescriptions: IIndustryMap<(corp: ICorporation) => React.Re
Recommended starting Industry: NO Recommended starting Industry: NO
</> </>
), ),
Agriculture: (corp: ICorporation) => ( Agriculture: (corp: Corporation) => (
<> <>
Cultivate crops and breed livestock to produce food. Cultivate crops and breed livestock to produce food.
<br /> <br />
@ -90,7 +90,7 @@ export const IndustryDescriptions: IIndustryMap<(corp: ICorporation) => React.Re
Recommended starting Industry: YES Recommended starting Industry: YES
</> </>
), ),
Fishing: (corp: ICorporation) => ( Fishing: (corp: Corporation) => (
<> <>
Produce food through the breeding and processing of fish and fish products. Produce food through the breeding and processing of fish and fish products.
<br /> <br />
@ -100,7 +100,7 @@ export const IndustryDescriptions: IIndustryMap<(corp: ICorporation) => React.Re
Recommended starting Industry: NO Recommended starting Industry: NO
</> </>
), ),
Mining: (corp: ICorporation) => ( Mining: (corp: Corporation) => (
<> <>
Extract and process metals from the earth. Extract and process metals from the earth.
<br /> <br />
@ -110,7 +110,7 @@ export const IndustryDescriptions: IIndustryMap<(corp: ICorporation) => React.Re
Recommended starting Industry: NO Recommended starting Industry: NO
</> </>
), ),
Food: (corp: ICorporation) => ( Food: (corp: Corporation) => (
<> <>
Create your own restaurants all around the world. Create your own restaurants all around the world.
<br /> <br />
@ -120,7 +120,7 @@ export const IndustryDescriptions: IIndustryMap<(corp: ICorporation) => React.Re
Recommended starting Industry: YES Recommended starting Industry: YES
</> </>
), ),
Tobacco: (corp: ICorporation) => ( Tobacco: (corp: Corporation) => (
<> <>
Create and distribute tobacco and tobacco-related products. Create and distribute tobacco and tobacco-related products.
<br /> <br />
@ -130,7 +130,7 @@ export const IndustryDescriptions: IIndustryMap<(corp: ICorporation) => React.Re
Recommended starting Industry: YES Recommended starting Industry: YES
</> </>
), ),
Chemical: (corp: ICorporation) => ( Chemical: (corp: Corporation) => (
<> <>
Produce industrial chemicals. Produce industrial chemicals.
<br /> <br />
@ -140,7 +140,7 @@ export const IndustryDescriptions: IIndustryMap<(corp: ICorporation) => React.Re
Recommended starting Industry: NO Recommended starting Industry: NO
</> </>
), ),
Pharmaceutical: (corp: ICorporation) => ( Pharmaceutical: (corp: Corporation) => (
<> <>
Discover, develop, and create new pharmaceutical drugs. Discover, develop, and create new pharmaceutical drugs.
<br /> <br />
@ -150,7 +150,7 @@ export const IndustryDescriptions: IIndustryMap<(corp: ICorporation) => React.Re
Recommended starting Industry: NO Recommended starting Industry: NO
</> </>
), ),
Computer: (corp: ICorporation) => ( Computer: (corp: Corporation) => (
<> <>
Develop and manufacture new computer hardware and networking infrastructures. Develop and manufacture new computer hardware and networking infrastructures.
<br /> <br />
@ -160,7 +160,7 @@ export const IndustryDescriptions: IIndustryMap<(corp: ICorporation) => React.Re
Recommended starting Industry: NO Recommended starting Industry: NO
</> </>
), ),
Robotics: (corp: ICorporation) => ( Robotics: (corp: Corporation) => (
<> <>
Develop and create robots. Develop and create robots.
<br /> <br />
@ -170,7 +170,7 @@ export const IndustryDescriptions: IIndustryMap<(corp: ICorporation) => React.Re
Recommended starting Industry: NO Recommended starting Industry: NO
</> </>
), ),
Software: (corp: ICorporation) => ( Software: (corp: Corporation) => (
<> <>
Develop computer software and create AI Cores. Develop computer software and create AI Cores.
<br /> <br />
@ -180,7 +180,7 @@ export const IndustryDescriptions: IIndustryMap<(corp: ICorporation) => React.Re
Recommended starting Industry: YES Recommended starting Industry: YES
</> </>
), ),
Healthcare: (corp: ICorporation) => ( Healthcare: (corp: Corporation) => (
<> <>
Create and manage hospitals. Create and manage hospitals.
<br /> <br />
@ -190,7 +190,7 @@ export const IndustryDescriptions: IIndustryMap<(corp: ICorporation) => React.Re
Recommended starting Industry: NO Recommended starting Industry: NO
</> </>
), ),
RealEstate: (corp: ICorporation) => ( RealEstate: (corp: Corporation) => (
<> <>
Develop and manage real estate properties. Develop and manage real estate properties.
<br /> <br />

@ -4,8 +4,8 @@ import { getRandomInt } from "../utils/helpers/getRandomInt";
import { generateRandomString } from "../utils/StringHelperFunctions"; import { generateRandomString } from "../utils/StringHelperFunctions";
import { Generic_fromJSON, Generic_toJSON, IReviverValue, Reviver } from "../utils/JSONReviver"; import { Generic_fromJSON, Generic_toJSON, IReviverValue, Reviver } from "../utils/JSONReviver";
import { Employee } from "./Employee"; import { Employee } from "./Employee";
import { IIndustry } from "./IIndustry"; import { Industry } from "./Industry";
import { ICorporation } from "./ICorporation"; import { Corporation } from "./Corporation";
interface IParams { interface IParams {
loc?: string; loc?: string;
@ -68,7 +68,7 @@ export class OfficeSpace {
return this.employees.length >= this.size; return this.employees.length >= this.size;
} }
process(marketCycles = 1, corporation: ICorporation, industry: IIndustry): number { process(marketCycles = 1, corporation: Corporation, industry: Industry): number {
// HRBuddy AutoRecruitment and training // HRBuddy AutoRecruitment and training
if (industry.hasResearch("HRBuddy-Recruitment") && !this.atCapacity()) { if (industry.hasResearch("HRBuddy-Recruitment") && !this.atCapacity()) {
const emp = this.hireRandomEmployee(); const emp = this.hireRandomEmployee();
@ -177,7 +177,7 @@ export class OfficeSpace {
} }
} }
calculateEmployeeProductivity(corporation: ICorporation, industry: IIndustry): void { calculateEmployeeProductivity(corporation: Corporation, industry: Industry): void {
//Reset //Reset
for (const name of Object.keys(this.employeeProd)) { for (const name of Object.keys(this.employeeProd)) {
this.employeeProd[name] = 0; this.employeeProd[name] = 0;

@ -1,6 +1,6 @@
import { EmployeePositions } from "./EmployeePositions"; import { EmployeePositions } from "./EmployeePositions";
import { MaterialSizes } from "./MaterialSizes"; import { MaterialSizes } from "./MaterialSizes";
import { IIndustry } from "./IIndustry"; import { Industry } from "./Industry";
import { ProductRatingWeights, IProductRatingWeight } from "./ProductRatingWeights"; import { ProductRatingWeights, IProductRatingWeight } from "./ProductRatingWeights";
import { createCityMap } from "../Locations/createCityMap"; import { createCityMap } from "../Locations/createCityMap";
@ -157,7 +157,7 @@ export class Product {
} }
// @param industry - Industry object. Reference to industry that makes this Product // @param industry - Industry object. Reference to industry that makes this Product
finishProduct(industry: IIndustry): void { finishProduct(industry: Industry): void {
this.fin = true; this.fin = true;
// Calculate properties // Calculate properties
@ -248,7 +248,7 @@ export class Product {
} }
} }
calculateRating(industry: IIndustry): void { calculateRating(industry: Industry): void {
const weights: IProductRatingWeight = ProductRatingWeights[industry.type]; const weights: IProductRatingWeight = ProductRatingWeights[industry.type];
if (weights == null) { if (weights == null) {
console.error(`Could not find product rating weights for: ${industry}`); console.error(`Could not find product rating weights for: ${industry}`);

@ -1,14 +1,14 @@
import { Material } from "./Material"; import { Material } from "./Material";
import { ICorporation } from "./ICorporation"; import { Corporation } from "./Corporation";
import { IIndustry } from "./IIndustry"; import { Industry } from "./Industry";
import { MaterialSizes } from "./MaterialSizes"; import { MaterialSizes } from "./MaterialSizes";
import { IMap } from "../types"; import { IMap } from "../types";
import { Generic_fromJSON, Generic_toJSON, IReviverValue, Reviver } from "../utils/JSONReviver"; import { Generic_fromJSON, Generic_toJSON, IReviverValue, Reviver } from "../utils/JSONReviver";
import { exceptionAlert } from "../utils/helpers/exceptionAlert"; import { exceptionAlert } from "../utils/helpers/exceptionAlert";
interface IConstructorParams { interface IConstructorParams {
corp?: ICorporation; corp?: Corporation;
industry?: IIndustry; industry?: Industry;
loc?: string; loc?: string;
size?: number; size?: number;
} }
@ -96,7 +96,7 @@ export class Warehouse {
} }
} }
updateSize(corporation: ICorporation, industry: IIndustry): void { updateSize(corporation: Corporation, industry: Industry): void {
try { try {
this.size = this.level * 100 * corporation.getStorageMultiplier() * industry.getStorageMultiplier(); this.size = this.level * 100 * corporation.getStorageMultiplier() * industry.getStorageMultiplier();
} catch (e: unknown) { } catch (e: unknown) {

@ -1,39 +1,7 @@
import { CityName } from "./../../Locations/data/CityNames"; import { CityName } from "./../../Locations/data/CityNames";
const CyclesPerMarketCycle = 50; const CyclesPerMarketCycle = 50;
const AllCorporationStates = ["START", "PURCHASE", "PRODUCTION", "SALE", "EXPORT"]; const AllCorporationStates = ["START", "PURCHASE", "PRODUCTION", "SALE", "EXPORT"];
export const CorporationConstants: { export const CorporationConstants = {
INITIALSHARES: number;
SHARESPERPRICEUPDATE: number;
IssueNewSharesCooldown: number;
SellSharesCooldown: number;
CyclesPerMarketCycle: number;
CyclesPerIndustryStateCycle: number;
SecsPerMarketCycle: number;
Cities: string[];
WarehouseInitialCost: number;
WarehouseInitialSize: number;
WarehouseUpgradeBaseCost: number;
OfficeInitialCost: number;
OfficeInitialSize: number;
OfficeUpgradeBaseCost: number;
BribeThreshold: number;
BribeToRepRatio: number;
ProductProductionCostRatio: number;
DividendMaxRate: number;
EmployeeSalaryMultiplier: number;
CyclesPerEmployeeRaise: number;
EmployeeRaiseAmount: number;
BaseMaxProducts: number;
AllCorporationStates: string[];
AllMaterials: string[];
AllIndustryTypes: string[];
AllUnlocks: string[];
AllUpgrades: string[];
AllResearch: string[];
FundingRoundShares: number[];
FundingRoundMultiplier: number[];
ValuationLength: number;
} = {
INITIALSHARES: 1e9, //Total number of shares you have at your company INITIALSHARES: 1e9, //Total number of shares you have at your company
SHARESPERPRICEUPDATE: 1e6, //When selling large number of shares, price is dynamically updated for every batch of this amount SHARESPERPRICEUPDATE: 1e6, //When selling large number of shares, price is dynamically updated for every batch of this amount
IssueNewSharesCooldown: 216e3, // 12 Hour in terms of game cycles IssueNewSharesCooldown: 216e3, // 12 Hour in terms of game cycles
@ -96,7 +64,7 @@ export const CorporationConstants: {
"Tobacco", "Tobacco",
"Chemical", "Chemical",
"Pharmaceutical", "Pharmaceutical",
"Hardware", "Computers",
"Robotics", "Robotics",
"Software", "Software",
"Healthcare", "Healthcare",

@ -1,14 +1,11 @@
import React, { useContext } from "react"; import React, { useContext } from "react";
import { ICorporation } from "../ICorporation"; import { Corporation } from "../Corporation";
import { IIndustry } from "../IIndustry"; import { Industry } from "../Industry";
export const Context: { export const Context = {
Corporation: React.Context<ICorporation>; Corporation: React.createContext<Corporation>({} as Corporation),
Division: React.Context<IIndustry>; Division: React.createContext<Industry>({} as Industry),
} = {
Corporation: React.createContext<ICorporation>({} as ICorporation),
Division: React.createContext<IIndustry>({} as IIndustry),
}; };
export const useCorporation = (): ICorporation => useContext(Context.Corporation); export const useCorporation = (): Corporation => useContext(Context.Corporation);
export const useDivision = (): IIndustry => useContext(Context.Division); export const useDivision = (): Industry => useContext(Context.Division);

@ -2,11 +2,11 @@
// These are the tabs at the top of the UI that let you switch to different // These are the tabs at the top of the UI that let you switch to different
// divisions, see an overview of your corporation, or create a new industry // divisions, see an overview of your corporation, or create a new industry
import React, { useState, useEffect } from "react"; import React, { useState, useEffect } from "react";
import { IIndustry } from "../IIndustry"; import { Industry } from "../Industry";
import { MainPanel } from "./MainPanel"; import { MainPanel } from "./MainPanel";
import { Industries } from "../IndustryData"; import { Industries } from "../IndustryData";
import { ExpandIndustryTab } from "./ExpandIndustryTab"; import { ExpandIndustryTab } from "./ExpandIndustryTab";
import { use } from "../../ui/Context"; import { Player } from "../../Player";
import { Context } from "./Context"; import { Context } from "./Context";
import { Overview } from "./Overview"; import { Overview } from "./Overview";
@ -14,8 +14,7 @@ import Tabs from "@mui/material/Tabs";
import Tab from "@mui/material/Tab"; import Tab from "@mui/material/Tab";
export function CorporationRoot(): React.ReactElement { export function CorporationRoot(): React.ReactElement {
const player = use.Player(); const corporation = Player.corporation;
const corporation = player.corporation;
if (corporation === null) return <></>; if (corporation === null) return <></>;
const setRerender = useState(false)[1]; const setRerender = useState(false)[1];
function rerender(): void { function rerender(): void {
@ -33,7 +32,7 @@ export function CorporationRoot(): React.ReactElement {
const canExpand = const canExpand =
Object.keys(Industries).filter( Object.keys(Industries).filter(
(industryType: string) => (industryType: string) =>
corporation.divisions.find((division: IIndustry) => division.type === industryType) === undefined, corporation.divisions.find((division: Industry) => division.type === industryType) === undefined,
).length > 0; ).length > 0;
return ( return (

@ -2,7 +2,7 @@ import React, { useState } from "react";
import { dialogBoxCreate } from "../../ui/React/DialogBox"; import { dialogBoxCreate } from "../../ui/React/DialogBox";
import { IndustryStartingCosts, Industries, IndustryDescriptions } from "../IndustryData"; import { IndustryStartingCosts, Industries, IndustryDescriptions } from "../IndustryData";
import { useCorporation } from "./Context"; import { useCorporation } from "./Context";
import { IIndustry } from "../IIndustry"; import { Industry } from "../Industry";
import { NewIndustry } from "../Actions"; import { NewIndustry } from "../Actions";
import Typography from "@mui/material/Typography"; import Typography from "@mui/material/Typography";
@ -23,7 +23,7 @@ export function ExpandIndustryTab(props: IProps): React.ReactElement {
const possibleIndustries = allIndustries const possibleIndustries = allIndustries
.filter( .filter(
(industryType: string) => (industryType: string) =>
corp.divisions.find((division: IIndustry) => division.type === industryType) === undefined, corp.divisions.find((division: Industry) => division.type === industryType) === undefined,
) )
.sort(); .sort();
const [industry, setIndustry] = useState(possibleIndustries.length > 0 ? possibleIndustries[0] : ""); const [industry, setIndustry] = useState(possibleIndustries.length > 0 ? possibleIndustries[0] : "");

@ -1,8 +1,8 @@
import { IIndustry } from "../IIndustry"; import { Industry } from "../Industry";
// Returns a boolean indicating whether the given material is relevant for the // Returns a boolean indicating whether the given material is relevant for the
// current industry. // current industry.
export function isRelevantMaterial(matName: string, division: IIndustry): boolean { export function isRelevantMaterial(matName: string, division: Industry): boolean {
// Materials that affect Production multiplier // Materials that affect Production multiplier
const prodMultiplierMats = ["Hardware", "Robots", "AICores", "RealEstate", "AI Cores", "Real Estate"]; const prodMultiplierMats = ["Hardware", "Robots", "AICores", "RealEstate", "AI Cores", "Real Estate"];

@ -7,7 +7,6 @@ import { IndustryOverview } from "./IndustryOverview";
import { IndustryWarehouse } from "./IndustryWarehouse"; import { IndustryWarehouse } from "./IndustryWarehouse";
import { Warehouse } from "../Warehouse"; import { Warehouse } from "../Warehouse";
import { OfficeSpace } from "../OfficeSpace"; import { OfficeSpace } from "../OfficeSpace";
import { use } from "../../ui/Context";
import { useCorporation, useDivision } from "./Context"; import { useCorporation, useDivision } from "./Context";
import Box from "@mui/material/Box"; import Box from "@mui/material/Box";
@ -19,7 +18,6 @@ interface IProps {
} }
export function Industry(props: IProps): React.ReactElement { export function Industry(props: IProps): React.ReactElement {
const player = use.Player();
const corp = useCorporation(); const corp = useCorporation();
const division = useDivision(); const division = useDivision();
return ( return (
@ -31,7 +29,6 @@ export function Industry(props: IProps): React.ReactElement {
<Box sx={{ width: "50%" }}> <Box sx={{ width: "50%" }}>
<IndustryWarehouse <IndustryWarehouse
rerender={props.rerender} rerender={props.rerender}
player={player}
corp={corp} corp={corp}
currentCity={props.city} currentCity={props.city}
division={division} division={division}

@ -1,9 +1,9 @@
import React from "react"; import React from "react";
import { IIndustry } from "../IIndustry"; import { Industry } from "../Industry";
import { MathJaxWrapper } from "../../MathJaxWrapper"; import { MathJaxWrapper } from "../../MathJaxWrapper";
interface IProps { interface IProps {
division: IIndustry; division: Industry;
} }
export function IndustryProductEquation(props: IProps): React.ReactElement { export function IndustryProductEquation(props: IProps): React.ReactElement {

@ -3,8 +3,6 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { CorporationConstants } from "../data/Constants"; import { CorporationConstants } from "../data/Constants";
import { Material } from "../Material";
import { Product } from "../Product";
import { Warehouse } from "../Warehouse"; import { Warehouse } from "../Warehouse";
import { SmartSupplyModal } from "./modals/SmartSupplyModal"; import { SmartSupplyModal } from "./modals/SmartSupplyModal";
import { ProductElem } from "./ProductElem"; import { ProductElem } from "./ProductElem";
@ -13,9 +11,8 @@ import { MaterialSizes } from "../MaterialSizes";
import { numeralWrapper } from "../../ui/numeralFormat"; import { numeralWrapper } from "../../ui/numeralFormat";
import { ICorporation } from "../ICorporation"; import { Corporation } from "../Corporation";
import { IIndustry } from "../IIndustry"; import { Industry } from "../Industry";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { MoneyCost } from "./MoneyCost"; import { MoneyCost } from "./MoneyCost";
import { isRelevantMaterial } from "./Helpers"; import { isRelevantMaterial } from "./Helpers";
import { IndustryProductEquation } from "./IndustryProductEquation"; import { IndustryProductEquation } from "./IndustryProductEquation";
@ -31,11 +28,10 @@ import makeStyles from "@mui/styles/makeStyles";
import createStyles from "@mui/styles/createStyles"; import createStyles from "@mui/styles/createStyles";
interface IProps { interface IProps {
corp: ICorporation; corp: Corporation;
division: IIndustry; division: Industry;
warehouse: Warehouse | 0; warehouse: Warehouse | 0;
currentCity: string; currentCity: string;
player: IPlayer;
rerender: () => void; rerender: () => void;
} }
@ -94,7 +90,7 @@ function WarehouseRoot(props: IProps): React.ReactElement {
// Create React components for materials // Create React components for materials
const mats = []; const mats = [];
for (const matName of Object.keys(props.warehouse.materials)) { for (const matName of Object.keys(props.warehouse.materials)) {
if (!(props.warehouse.materials[matName] instanceof Material)) continue; if (!props.warehouse.materials[matName]) continue;
// Only create UI for materials that are relevant for the industry or in stock // Only create UI for materials that are relevant for the industry or in stock
const isInStock = props.warehouse.materials[matName].qty > 0; const isInStock = props.warehouse.materials[matName].qty > 0;
const isRelevant = isRelevantMaterial(matName, division); const isRelevant = isRelevantMaterial(matName, division);
@ -115,7 +111,7 @@ function WarehouseRoot(props: IProps): React.ReactElement {
if (division.makesProducts && Object.keys(division.products).length > 0) { if (division.makesProducts && Object.keys(division.products).length > 0) {
for (const productName of Object.keys(division.products)) { for (const productName of Object.keys(division.products)) {
const product = division.products[productName]; const product = division.products[productName];
if (!(product instanceof Product)) continue; if (!product) continue;
products.push( products.push(
<ProductElem rerender={props.rerender} city={props.currentCity} key={productName} product={product} />, <ProductElem rerender={props.rerender} city={props.currentCity} key={productName} product={product} />,
); );
@ -219,7 +215,7 @@ function WarehouseRoot(props: IProps): React.ReactElement {
} }
export function IndustryWarehouse(props: IProps): React.ReactElement { export function IndustryWarehouse(props: IProps): React.ReactElement {
if (props.warehouse instanceof Warehouse) { if (props.warehouse) {
return <WarehouseRoot {...props} />; return <WarehouseRoot {...props} />;
} else { } else {
return <EmptyWarehouse rerender={props.rerender} city={props.currentCity} />; return <EmptyWarehouse rerender={props.rerender} city={props.currentCity} />;

@ -4,7 +4,7 @@
import React from "react"; import React from "react";
import { CityTabs } from "./CityTabs"; import { CityTabs } from "./CityTabs";
import { IIndustry } from "../IIndustry"; import { Industry } from "../Industry";
import { Context, useCorporation } from "./Context"; import { Context, useCorporation } from "./Context";
import { CityName } from "../../Locations/data/CityNames"; import { CityName } from "../../Locations/data/CityNames";
@ -18,7 +18,7 @@ export function MainPanel(props: IProps): React.ReactElement {
const corp = useCorporation(); const corp = useCorporation();
const division = const division =
props.divisionName !== "Overview" props.divisionName !== "Overview"
? corp.divisions.find((division: IIndustry) => division.name === props.divisionName) ? corp.divisions.find((division: Industry) => division.name === props.divisionName)
: undefined; // use undefined because find returns undefined : undefined; // use undefined because find returns undefined
if (division === undefined) throw new Error("Cannot find division"); if (division === undefined) throw new Error("Cannot find division");

@ -2,7 +2,6 @@
// (right-side panel in the Industry UI) // (right-side panel in the Industry UI)
import React, { useState } from "react"; import React, { useState } from "react";
import { OfficeSpace } from "../OfficeSpace";
import { Material } from "../Material"; import { Material } from "../Material";
import { Warehouse } from "../Warehouse"; import { Warehouse } from "../Warehouse";
import { ExportModal } from "./modals/ExportModal"; import { ExportModal } from "./modals/ExportModal";
@ -45,7 +44,7 @@ export function MaterialElem(props: IMaterialProps): React.ReactElement {
const mat = props.mat; const mat = props.mat;
const markupLimit = mat.getMarkupLimit(); const markupLimit = mat.getMarkupLimit();
const office = division.offices[city]; const office = division.offices[city];
if (!(office instanceof OfficeSpace)) { if (!office) {
throw new Error(`Could not get OfficeSpace object for this city (${city})`); throw new Error(`Could not get OfficeSpace object for this city (${city})`);
} }

@ -1,6 +1,6 @@
import * as React from "react"; import * as React from "react";
import { numeralWrapper } from "../../ui/numeralFormat"; import { numeralWrapper } from "../../ui/numeralFormat";
import { ICorporation } from "../ICorporation"; import { Corporation } from "../Corporation";
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";
@ -18,7 +18,7 @@ const useStyles = makeStyles((theme: Theme) =>
interface IProps { interface IProps {
money: number; money: number;
corp: ICorporation; corp: Corporation;
} }
export function MoneyCost(props: IProps): React.ReactElement { export function MoneyCost(props: IProps): React.ReactElement {

@ -21,7 +21,7 @@ import { convertTimeMsToTimeElapsedString } from "../../utils/StringHelperFuncti
import { Money } from "../../ui/React/Money"; import { Money } from "../../ui/React/Money";
import { MoneyRate } from "../../ui/React/MoneyRate"; import { MoneyRate } from "../../ui/React/MoneyRate";
import { StatsTable } from "../../ui/React/StatsTable"; import { StatsTable } from "../../ui/React/StatsTable";
import { use } from "../../ui/Context"; import { Player } from "../../Player";
import { useCorporation } from "./Context"; import { useCorporation } from "./Context";
import Typography from "@mui/material/Typography"; import Typography from "@mui/material/Typography";
import Tooltip from "@mui/material/Tooltip"; import Tooltip from "@mui/material/Tooltip";
@ -34,7 +34,6 @@ interface IProps {
rerender: () => void; rerender: () => void;
} }
export function Overview({ rerender }: IProps): React.ReactElement { export function Overview({ rerender }: IProps): React.ReactElement {
const player = use.Player();
const corp = useCorporation(); const corp = useCorporation();
const profit: number = corp.revenue - corp.expenses; const profit: number = corp.revenue - corp.expenses;
@ -100,7 +99,7 @@ export function Overview({ rerender }: IProps): React.ReactElement {
</Typography> </Typography>
} }
> >
<Button onClick={() => corp.getStarterGuide(player)}>Getting Started Guide</Button> <Button onClick={() => corp.getStarterGuide()}>Getting Started Guide</Button>
</Tooltip> </Tooltip>
{corp.public ? <PublicButtons rerender={rerender} /> : <PrivateButtons rerender={rerender} />} {corp.public ? <PublicButtons rerender={rerender} /> : <PrivateButtons rerender={rerender} />}
<BribeButton /> <BribeButton />
@ -240,12 +239,11 @@ function PublicButtons({ rerender }: IPublicButtonsProps): React.ReactElement {
} }
function BribeButton(): React.ReactElement { function BribeButton(): React.ReactElement {
const player = use.Player();
const corp = useCorporation(); const corp = useCorporation();
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const canBribe = const canBribe =
corp.valuation >= CorporationConstants.BribeThreshold && corp.valuation >= CorporationConstants.BribeThreshold &&
player.factions.filter((f) => Factions[f].getInfo().offersWork()).length > 0; Player.factions.filter((f) => Factions[f].getInfo().offersWork()).length > 0;
function openBribe(): void { function openBribe(): void {
if (!canBribe) return; if (!canBribe) return;

@ -4,7 +4,7 @@ import { CorporationConstants } from "../../data/Constants";
import { numeralWrapper } from "../../../ui/numeralFormat"; import { numeralWrapper } from "../../../ui/numeralFormat";
import { dialogBoxCreate } from "../../../ui/React/DialogBox"; import { dialogBoxCreate } from "../../../ui/React/DialogBox";
import { Modal } from "../../../ui/React/Modal"; import { Modal } from "../../../ui/React/Modal";
import { use } from "../../../ui/Context"; import { Player } from "../../../Player";
import { useCorporation } from "../Context"; import { useCorporation } from "../Context";
import Typography from "@mui/material/Typography"; import Typography from "@mui/material/Typography";
import Button from "@mui/material/Button"; import Button from "@mui/material/Button";
@ -19,11 +19,10 @@ interface IProps {
} }
export function BribeFactionModal(props: IProps): React.ReactElement { export function BribeFactionModal(props: IProps): React.ReactElement {
const player = use.Player(); const factions = Player.factions.filter((name: string) => {
const factions = player.factions.filter((name: string) => {
const info = Factions[name].getInfo(); const info = Factions[name].getInfo();
if (!info.offersWork()) return false; if (!info.offersWork()) return false;
if (player.hasGangWith(name)) return false; if (Player.hasGangWith(name)) return false;
return true; return true;
}); });
const corp = useCorporation(); const corp = useCorporation();
@ -60,9 +59,7 @@ export function BribeFactionModal(props: IProps): React.ReactElement {
const fac = Factions[selectedFaction]; const fac = Factions[selectedFaction];
if (disabled) return; if (disabled) return;
const rep = repGain(money); const rep = repGain(money);
dialogBoxCreate( dialogBoxCreate(`You gained ${numeralWrapper.formatReputation(rep)} reputation with ${fac.name} by bribing them.`);
"You gained " + numeralWrapper.formatReputation(rep) + " reputation with " + fac.name + " by bribing them.",
);
fac.playerReputation += rep; fac.playerReputation += rep;
corp.funds = corp.funds - money; corp.funds = corp.funds - money;
props.onClose(); props.onClose();
@ -79,7 +76,7 @@ export function BribeFactionModal(props: IProps): React.ReactElement {
{factions.map((name: string) => { {factions.map((name: string) => {
const info = Factions[name].getInfo(); const info = Factions[name].getInfo();
if (!info.offersWork()) return; if (!info.offersWork()) return;
if (player.hasGangWith(name)) return; if (Player.hasGangWith(name)) return;
return ( return (
<MenuItem key={name} value={name}> <MenuItem key={name} value={name}>
{name} {name}

@ -1,7 +1,7 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { Modal } from "../../../ui/React/Modal"; import { Modal } from "../../../ui/React/Modal";
import { numeralWrapper } from "../../../ui/numeralFormat"; import { numeralWrapper } from "../../../ui/numeralFormat";
import { use } from "../../../ui/Context"; import { Player } from "../../../Player";
import { useCorporation } from "../Context"; import { useCorporation } from "../Context";
import Typography from "@mui/material/Typography"; import Typography from "@mui/material/Typography";
import Button from "@mui/material/Button"; import Button from "@mui/material/Button";
@ -19,7 +19,6 @@ interface IProps {
// Create a popup that lets the player buyback shares // Create a popup that lets the player buyback shares
// This is created when the player clicks the "Buyback Shares" button in the overview panel // This is created when the player clicks the "Buyback Shares" button in the overview panel
export function BuybackSharesModal(props: IProps): React.ReactElement { export function BuybackSharesModal(props: IProps): React.ReactElement {
const player = use.Player();
const corp = useCorporation(); const corp = useCorporation();
const [shares, setShares] = useState<number>(NaN); const [shares, setShares] = useState<number>(NaN);
@ -30,12 +29,12 @@ export function BuybackSharesModal(props: IProps): React.ReactElement {
isNaN(shares) || isNaN(shares) ||
shares <= 0 || shares <= 0 ||
shares > corp.issuedShares || shares > corp.issuedShares ||
shares * buybackPrice > player.money; shares * buybackPrice > Player.money;
function buy(): void { function buy(): void {
if (disabled) return; if (disabled) return;
try { try {
BuyBackShares(corp, player, shares); BuyBackShares(corp, shares);
} catch (err) { } catch (err) {
dialogBoxCreate(err + ""); dialogBoxCreate(err + "");
} }

@ -2,7 +2,8 @@ import React, { useState } from "react";
import { Money } from "../../../ui/React/Money"; import { Money } from "../../../ui/React/Money";
import { Modal } from "../../../ui/React/Modal"; import { Modal } from "../../../ui/React/Modal";
import { use } from "../../../ui/Context"; import { Router } from "../../../ui/GameRoot";
import { Player } from "../../../Player";
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 TextField from "@mui/material/TextField"; import TextField from "@mui/material/TextField";
@ -13,10 +14,8 @@ interface IProps {
} }
export function CreateCorporationModal(props: IProps): React.ReactElement { export function CreateCorporationModal(props: IProps): React.ReactElement {
const player = use.Player(); const canSelfFund = Player.canAfford(150e9);
const router = use.Router(); if (!Player.canAccessCorporation() || Player.hasCorporation()) {
const canSelfFund = player.canAfford(150e9);
if (!player.canAccessCorporation() || player.hasCorporation()) {
props.onClose(); props.onClose();
return <></>; return <></>;
} }
@ -35,11 +34,11 @@ export function CreateCorporationModal(props: IProps): React.ReactElement {
return; return;
} }
player.startCorporation(name); Player.startCorporation(name);
player.loseMoney(150e9, "corporation"); Player.loseMoney(150e9, "corporation");
props.onClose(); props.onClose();
router.toCorporation(); Router.toCorporation();
} }
function seed(): void { function seed(): void {
@ -47,17 +46,17 @@ export function CreateCorporationModal(props: IProps): React.ReactElement {
return; return;
} }
player.startCorporation(name, 500e6); Player.startCorporation(name, 500e6);
props.onClose(); props.onClose();
router.toCorporation(); Router.toCorporation();
} }
return ( return (
<Modal open={props.open} onClose={props.onClose}> <Modal open={props.open} onClose={props.onClose}>
<Typography> <Typography>
Would you like to start a corporation? This will require $150b for registration and initial funding.{" "} Would you like to start a corporation? This will require $150b for registration and initial funding.{" "}
{player.bitNodeN === 3 && {Player.bitNodeN === 3 &&
`This $150b `This $150b
can either be self-funded, or you can obtain the seed money from the government in exchange for 500 million can either be self-funded, or you can obtain the seed money from the government in exchange for 500 million
shares`} shares`}
@ -66,13 +65,13 @@ export function CreateCorporationModal(props: IProps): React.ReactElement {
If you would like to start one, please enter a name for your corporation below: If you would like to start one, please enter a name for your corporation below:
</Typography> </Typography>
<TextField autoFocus={true} placeholder="Corporation Name" onChange={onChange} value={name} /> <TextField autoFocus={true} placeholder="Corporation Name" onChange={onChange} value={name} />
{player.bitNodeN === 3 && ( {Player.bitNodeN === 3 && (
<Button onClick={seed} disabled={name == ""}> <Button onClick={seed} disabled={name == ""}>
Use seed money Use seed money
</Button> </Button>
)} )}
<Button onClick={selfFund} disabled={name == "" || !canSelfFund}> <Button onClick={selfFund} disabled={name == "" || !canSelfFund}>
Self-Fund (<Money money={150e9} player={player} />) Self-Fund (<Money money={150e9} forPurchase={true} />)
</Button> </Button>
</Modal> </Modal>
); );

@ -2,7 +2,7 @@ import React, { useState } from "react";
import { dialogBoxCreate } from "../../../ui/React/DialogBox"; import { dialogBoxCreate } from "../../../ui/React/DialogBox";
import { Material } from "../../Material"; import { Material } from "../../Material";
import { Export } from "../../Export"; import { Export } from "../../Export";
import { IIndustry } from "../../IIndustry"; import { Industry } from "../../Industry";
import { ExportMaterial } from "../../Actions"; import { ExportMaterial } from "../../Actions";
import { Modal } from "../../../ui/React/Modal"; import { Modal } from "../../../ui/React/Modal";
import { useCorporation } from "../Context"; import { useCorporation } from "../Context";
@ -23,9 +23,7 @@ interface IProps {
// Create a popup that lets the player manage exports // Create a popup that lets the player manage exports
export function ExportModal(props: IProps): React.ReactElement { export function ExportModal(props: IProps): React.ReactElement {
const corp = useCorporation(); const corp = useCorporation();
const possibleDivisions = corp.divisions.filter((division: IIndustry) => const possibleDivisions = corp.divisions.filter((division: Industry) => isRelevantMaterial(props.mat.name, division));
isRelevantMaterial(props.mat.name, division),
);
if (possibleDivisions.length === 0) throw new Error("Export popup created with no divisions."); if (possibleDivisions.length === 0) throw new Error("Export popup created with no divisions.");
const defaultDivision = possibleDivisions[0]; const defaultDivision = possibleDivisions[0];
if (Object.keys(defaultDivision.warehouses).length === 0) if (Object.keys(defaultDivision.warehouses).length === 0)
@ -72,7 +70,7 @@ export function ExportModal(props: IProps): React.ReactElement {
rerender(); rerender();
} }
const currentDivision = corp.divisions.find((division: IIndustry) => division.name === industry); const currentDivision = corp.divisions.find((division: Industry) => division.name === industry);
if (currentDivision === undefined) if (currentDivision === undefined)
throw new Error(`Export popup somehow ended up with undefined division '${currentDivision}'`); throw new Error(`Export popup somehow ended up with undefined division '${currentDivision}'`);
const possibleCities = Object.keys(currentDivision.warehouses).filter( const possibleCities = Object.keys(currentDivision.warehouses).filter(
@ -90,8 +88,8 @@ export function ExportModal(props: IProps): React.ReactElement {
</Typography> </Typography>
<Select onChange={onIndustryChange} value={industry}> <Select onChange={onIndustryChange} value={industry}>
{corp.divisions {corp.divisions
.filter((division: IIndustry) => isRelevantMaterial(props.mat.name, division)) .filter((division: Industry) => isRelevantMaterial(props.mat.name, division))
.map((division: IIndustry) => ( .map((division: Industry) => (
<MenuItem key={division.name} value={division.name}> <MenuItem key={division.name} value={division.name}>
{division.name} {division.name}
</MenuItem> </MenuItem>

@ -89,14 +89,11 @@ export function IssueNewSharesModal(props: IProps): React.ReactElement {
let dialogContents = let dialogContents =
`Issued ${numeralWrapper.format(newShares, "0.000a")} new shares` + `Issued ${numeralWrapper.format(newShares, "0.000a")} new shares` +
` and raised ${numeralWrapper.formatMoney(profit)}.`; ` and raised ${numeralWrapper.formatMoney(profit)}.` +
if (privateShares > 0) { (privateShares > 0)
dialogContents += `<br>${numeralWrapper.format( ? "\n" + numeralWrapper.format(privateShares, "0.000a") + "of these shares were bought by private investors."
privateShares, : "";
"0.000a", dialogContents += `\n\nStock price decreased to ${numeralWrapper.formatMoney(corp.sharePrice)}`;
)} of these shares were bought by private investors.`;
}
dialogContents += `<br><br>Stock price decreased to ${numeralWrapper.formatMoney(corp.sharePrice)}`;
dialogBoxCreate(dialogContents); dialogBoxCreate(dialogContents);
} }

@ -2,7 +2,7 @@ import React, { useState } from "react";
import { Modal } from "../../../ui/React/Modal"; import { Modal } from "../../../ui/React/Modal";
import { IndustryResearchTrees } from "../../IndustryData"; import { IndustryResearchTrees } from "../../IndustryData";
import { CorporationConstants } from "../../data/Constants"; import { CorporationConstants } from "../../data/Constants";
import { IIndustry } from "../../IIndustry"; import { Industry } from "../../Industry";
import { Research } from "../../Actions"; import { Research } from "../../Actions";
import { Node } from "../../ResearchTree"; import { Node } from "../../ResearchTree";
import { ResearchMap } from "../../ResearchMap"; import { ResearchMap } from "../../ResearchMap";
@ -20,7 +20,7 @@ import CheckIcon from "@mui/icons-material/Check";
interface INodeProps { interface INodeProps {
n: Node | null; n: Node | null;
division: IIndustry; division: Industry;
} }
function Upgrade({ n, division }: INodeProps): React.ReactElement { function Upgrade({ n, division }: INodeProps): React.ReactElement {
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
@ -42,9 +42,7 @@ function Upgrade({ n, division }: INodeProps): React.ReactElement {
} }
dialogBoxCreate( dialogBoxCreate(
`Researched ${n.text}. It may take a market cycle ` + `Researched ${n.text}. It may take a market cycle (~${CorporationConstants.SecsPerMarketCycle} seconds) before the effects of the Research apply.`,
`(~${CorporationConstants.SecsPerMarketCycle} seconds) before the effects of ` +
`the Research apply.`,
); );
} }
@ -131,7 +129,7 @@ function Upgrade({ n, division }: INodeProps): React.ReactElement {
interface IProps { interface IProps {
open: boolean; open: boolean;
onClose: () => void; onClose: () => void;
industry: IIndustry; industry: Industry;
} }
// Create the Research Tree UI for this Industry // Create the Research Tree UI for this Industry

@ -2,9 +2,8 @@ import React, { useState } from "react";
import { numeralWrapper } from "../../../ui/numeralFormat"; import { numeralWrapper } from "../../../ui/numeralFormat";
import { dialogBoxCreate } from "../../../ui/React/DialogBox"; import { dialogBoxCreate } from "../../../ui/React/DialogBox";
import { Modal } from "../../../ui/React/Modal"; import { Modal } from "../../../ui/React/Modal";
import { use } from "../../../ui/Context";
import { useCorporation } from "../Context"; import { useCorporation } from "../Context";
import { ICorporation } from "../../ICorporation"; import { Corporation } from "../../Corporation";
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 { Money } from "../../../ui/React/Money"; import { Money } from "../../../ui/React/Money";
@ -20,13 +19,12 @@ interface IProps {
// Create a popup that lets the player sell Corporation shares // Create a popup that lets the player sell Corporation shares
// This is created when the player clicks the "Sell Shares" button in the overview panel // This is created when the player clicks the "Sell Shares" button in the overview panel
export function SellSharesModal(props: IProps): React.ReactElement { export function SellSharesModal(props: IProps): React.ReactElement {
const player = use.Player();
const corp = useCorporation(); const corp = useCorporation();
const [shares, setShares] = useState<number>(NaN); const [shares, setShares] = useState<number>(NaN);
const disabled = isNaN(shares) || shares <= 0 || shares > corp.numShares; const disabled = isNaN(shares) || shares <= 0 || shares > corp.numShares;
function ProfitIndicator(props: { shares: number | null; corp: ICorporation }): React.ReactElement { function ProfitIndicator(props: { shares: number | null; corp: Corporation }): React.ReactElement {
if (props.shares === null) return <></>; if (props.shares === null) return <></>;
let text = ""; let text = "";
if (isNaN(props.shares) || props.shares <= 0) { if (isNaN(props.shares) || props.shares <= 0) {
@ -49,7 +47,7 @@ export function SellSharesModal(props: IProps): React.ReactElement {
function sell(): void { function sell(): void {
if (disabled) return; if (disabled) return;
try { try {
const profit = SellShares(corp, player, shares); const profit = SellShares(corp, shares);
props.onClose(); props.onClose();
dialogBoxCreate( dialogBoxCreate(
<> <>

@ -2,7 +2,6 @@ import React, { useState } from "react";
import { Warehouse } from "../../Warehouse"; import { Warehouse } from "../../Warehouse";
import { SetSmartSupply, SetSmartSupplyUseLeftovers } from "../../Actions"; import { SetSmartSupply, SetSmartSupplyUseLeftovers } from "../../Actions";
import { Material } from "../../Material";
import { dialogBoxCreate } from "../../../ui/React/DialogBox"; import { dialogBoxCreate } from "../../../ui/React/DialogBox";
import { Modal } from "../../../ui/React/Modal"; import { Modal } from "../../../ui/React/Modal";
import { useDivision } from "../Context"; import { useDivision } from "../Context";
@ -62,7 +61,7 @@ export function SmartSupplyModal(props: IProps): React.ReactElement {
// Create React components for materials // Create React components for materials
const mats = []; const mats = [];
for (const matName of Object.keys(props.warehouse.materials)) { for (const matName of Object.keys(props.warehouse.materials)) {
if (!(props.warehouse.materials[matName] instanceof Material)) continue; if (!props.warehouse.materials[matName]) continue;
if (!Object.keys(division.reqMats).includes(matName)) continue; if (!Object.keys(division.reqMats).includes(matName)) continue;
mats.push(<Leftover key={matName} warehouse={props.warehouse} matName={matName} />); mats.push(<Leftover key={matName} warehouse={props.warehouse} matName={matName} />);
} }

@ -41,8 +41,7 @@ export function ThrowPartyModal(props: IProps): React.ReactElement {
if (mult > 0) { if (mult > 0) {
dialogBoxCreate( dialogBoxCreate(
"You threw a party for the office! The morale and happiness " + "You threw a party for the office! The morale and happiness of each employee increased by " +
"of each employee increased by " +
numeralWrapper.formatPercentage(mult - 1), numeralWrapper.formatPercentage(mult - 1),
); );
} }

@ -2,7 +2,7 @@ import React from "react";
import { numeralWrapper } from "../../../ui/numeralFormat"; import { numeralWrapper } from "../../../ui/numeralFormat";
import { CorporationConstants } from "../../data/Constants"; import { CorporationConstants } from "../../data/Constants";
import { OfficeSpace } from "../../OfficeSpace"; import { OfficeSpace } from "../../OfficeSpace";
import { ICorporation } from "../../ICorporation"; import { Corporation } from "../../Corporation";
import { UpgradeOfficeSize } from "../../Actions"; import { UpgradeOfficeSize } from "../../Actions";
import { Modal } from "../../../ui/React/Modal"; import { Modal } from "../../../ui/React/Modal";
import { useCorporation } from "../Context"; import { useCorporation } from "../Context";
@ -14,7 +14,7 @@ import Box from "@mui/material/Box";
interface IUpgradeButton { interface IUpgradeButton {
cost: number; cost: number;
size: number; size: number;
corp: ICorporation; corp: Corporation;
office: OfficeSpace; office: OfficeSpace;
onClose: () => void; onClose: () => void;
rerender: () => void; rerender: () => void;

29
src/CotMG/BaseGift.ts Normal file

@ -0,0 +1,29 @@
import { ActiveFragment } from "./ActiveFragment";
export class BaseGift {
fragments: ActiveFragment[];
_width?: number;
_height?: number;
constructor(width?: number, height?: number, fragments: ActiveFragment[] = []) {
this.fragments = fragments;
this._width = width;
this._height = height;
}
width(): number {
return this._width || 4;
}
height(): number {
return this._height || 4;
}
fragmentAt(worldX: number, worldY: number): ActiveFragment | undefined {
for (const aFrag of this.fragments) {
if (aFrag.fullAt(worldX, worldY)) {
return aFrag;
}
}
return undefined;
}
}

@ -1,67 +0,0 @@
import { ActiveFragment } from "./ActiveFragment";
import { IStaneksGift } from "./IStaneksGift";
export class DummyGift implements IStaneksGift {
storedCycles = 0;
fragments: ActiveFragment[] = [];
_width: number;
_height: number;
constructor(width: number, height: number, fragments: ActiveFragment[]) {
this.fragments = fragments;
this._width = width;
this._height = height;
}
width(): number {
return this._width;
}
height(): number {
return this._height;
}
charge(): void {
throw new Error("unimplemented for dummy gift");
}
process(): void {
throw new Error("unimplemented for dummy gift");
}
effect(): number {
throw new Error("unimplemented for dummy gift");
}
canPlace(): boolean {
throw new Error("unimplemented for dummy gift");
}
place(): boolean {
throw new Error("unimplemented for dummy gift");
}
findFragment(): ActiveFragment | undefined {
throw new Error("unimplemented for dummy gift");
}
fragmentAt(worldX: number, worldY: number): ActiveFragment | undefined {
for (const aFrag of this.fragments) {
if (aFrag.fullAt(worldX, worldY)) {
return aFrag;
}
}
return undefined;
}
delete(): boolean {
throw new Error("unimplemented for dummy gift");
}
clear(): void {
throw new Error("unimplemented for dummy gift");
}
count(): number {
throw new Error("unimplemented for dummy gift");
}
inBonus(): boolean {
throw new Error("unimplemented for dummy gift");
}
prestigeAugmentation(): void {
throw new Error("unimplemented for dummy gift");
}
prestigeSourceFile(): void {
throw new Error("unimplemented for dummy gift");
}
}

Some files were not shown because too many files have changed in this diff Show More