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
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:
- [Discord](https://discord.gg/XKEGvHqVr3)
- [Discord](https://discord.gg/XKEGvHqVr3).
There is a dedicated Discord instance set up for more free-form chats
between all members of the community. Regular players, heavy scripters,
Bitburner contributors, and everyone in between can be found on the
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
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
an unexpected behavior within the game. Or because the game is about
scripting perhaps there is something that is conflicting with the
browser's Javascript interaction. So please do not be afraid to open a
[new issue](https://github.com/danielyxie/bitburner/issues/new).
browser's JavaScript interaction. So please do not be afraid to open a
[new Issue](https://github.com/danielyxie/bitburner/issues/new).
## 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
- **Use a clear and descriptive title** for the issue
- **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
- **Use a clear and descriptive title** for the Issue.
- **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.
Zip your save file first, then attach the zipped save file.
- **Provide instructions on how to reproduce the bug** in as much detail
as possible. If you cannot reliably reproduce the bug, then just try
your best to explain what was happening when the bug occurred
- **Provide any scripts** that triggered the bug if the issue is Netscript-related
your best to explain what was happening when the bug occurred.
- **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**
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
@ -71,13 +72,13 @@ changes are okay to contribute:
##### Contributions that Will Most Likely Be Accepted
- Bug Fixes
- Quality-of-Life Changes
- Bug fixes
- Quality-of-life changes
- Adding a new, commonly-requested Netscript function
- Fixing or improving UI elements
- Adding game settings/options
- 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
@ -88,67 +89,108 @@ changes are okay to contribute:
## 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.
```
# This will add the game original code as a repo in your local copy
$ git remote add danielyxie git@github.com:danielyxie/bitburner.git
- Web browser. Log in to your GitHub account, navigate to the
[game's repository](https://github.com/danielyxie/bitburner), and fork the
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
$ git remote show
danielyxie
origin
```sh
# This clones the game's code repository. The output you get might vary.
$ git clone https://github.com/danielyxie/bitburner.git
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)
$ git fetch danielyxie
From github.com:danielyxie/bitburner
* [new branch] dev -> danielyxie/dev
* [new branch] master -> danielyxie/master
# Change to the directory that contains your local copy.
$ cd bitburner
# 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
- 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.
- 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
- `npm` (maybe via `nvm`)
- Github Desktop (windows only)
- Visual Studio code (optional)
- Github Desktop (Windows only)
- Visual Studio Code (optional)
Inside the root of the repo run
`npm install` to install all the dependencies
`npm run start:dev` to launch the game in dev mode.
Inside the root of the repository run:
- `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.
Saving a file will reload the game automatically.
### How to build the electron app
Tested on Node v16.13.1 (LTS) on Windows
These steps only work in a bash-like environment, like MinGW for Windows.
Tested on Node v16.13.1 (LTS) on Windows.
These steps only work in a Bash-like environment, like MinGW for Windows.
```sh
# Install the main game dependencies & build the app in debug mode
npm install
npm run build:dev
# Install the main game dependencies & build the app in debug mode.
$ npm install
$ npm run build:dev
# Use electron-packager to build the app to the .build/ folder
npm run electron
# Use electron-packager to build the app to the .build/ folder.
$ npm run electron
# When launching the .exe directly, you'll need the steam_appid.txt file in the root
# 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
# When launching the .exe directly, you'll need the steam_appid.txt file in the root.
# 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
# And run the game...
.build/bitburner-win32-x64/bitburner.exe
$ .build/bitburner-win32-x64/bitburner.exe
```
### 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
the following rules:
- Work in a branch forked from `dev` to isolate the new code
- Ensure you have latest from the [game's main
repository](danielyxie/bitburner@dev)
- Rebase your branch if necessary
- Run the game locally to test out your changes
- Work in a branch forked from `dev` to isolate the new code.
- Ensure you have the latest from the [game's main
repository](../../../tree/dev).
- Rebase your branch if necessary.
- Run the game locally to test out your changes.
- When submitting the pull request, make sure that the base fork is
_danielyxie/bitburner_ and the base is _dev_.
- 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
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
@ -174,23 +216,23 @@ the following rules:
in the root of the repository. These will be updated as part of official
releases.
## As a Documentor
## As a Documenter
To contribute to and view your changes to the BitBurner documentation on [Read The
Docs](http://bitburner.readthedocs.io/), you will
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
rules:
- Work in a branch forked from `dev` to isolate the new code
- Ensure you have latest from the [game's main
repository](danielyxie/bitburner@dev)
- Rebase your branch if necessary
- Work in a branch forked from `dev` to isolate the new code.
- Ensure you have the latest from the [game's main
repository](../../../tree/dev).
- Rebase your branch if necessary.
- When submitting the pull request, make sure that the base fork is
_danielyxie/bitburner_ and the base is _dev_.
- Do not check in any generated files under `doc\`. The documentation is built
@ -204,5 +246,5 @@ Update the following
- `package.json` `version`
- `doc/source/conf.py` `version` and `release`
- `doc/source/changelog.rst`
- post to discord
- post to Discord
- post to reddit.com/r/Bitburner

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

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

@ -1,6 +1,6 @@
import React, { useEffect } from "react";
import Typography from "@mui/material/Typography";
import { use } from "../../ui/Context";
import { Player } from "../../Player";
import { Exploit } from "../../Exploits/Exploit";
const metaBB = "https://bitburner-official.github.io/bitburner-legacy/";
@ -12,11 +12,10 @@ const style = {
};
export function BBCabinetRoot(): React.ReactElement {
const player = use.Player();
useEffect(() => {
window.addEventListener("message", function (this: Window, ev: MessageEvent<boolean>) {
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 { FactionNames } from "../Faction/data/FactionNames";
import { IPlayer } from "../PersonObjects/IPlayer";
import { Player } from "../Player";
import { AugmentationNames } from "./data/AugmentationNames";
import { CONSTANTS } from "../Constants";
import { StaticAugmentations } from "./StaticAugmentations";
@ -531,26 +531,26 @@ export class Augmentation {
}
}
getCost(player: IPlayer): AugmentationCosts {
getCost(): AugmentationCosts {
const augmentationReference = StaticAugmentations[this.name];
let moneyCost = augmentationReference.baseCost;
let repCost = augmentationReference.baseRepRequirement;
if (augmentationReference.name === AugmentationNames.NeuroFluxGovernor) {
let nextLevel = this.getLevel(player);
let nextLevel = this.getLevel();
--nextLevel;
const multiplier = Math.pow(CONSTANTS.NeuroFluxGovernorLevelMult, nextLevel);
repCost = augmentationReference.baseRepRequirement * multiplier * BitNodeMultipliers.AugmentationRepCost;
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();
}
} else if (augmentationReference.factions.includes(FactionNames.ShadowsOfAnarchy)) {
const soaAugmentationNames = initSoAAugmentations().map((augmentation) => augmentation.name);
const soaMultiplier = Math.pow(
CONSTANTS.SoACostMult,
soaAugmentationNames.filter((augmentationName) => player.hasAugmentation(augmentationName)).length,
soaAugmentationNames.filter((augmentationName) => Player.hasAugmentation(augmentationName)).length,
);
moneyCost = augmentationReference.baseCost * soaMultiplier;
if (soaAugmentationNames.find((augmentationName) => augmentationName === augmentationReference.name)) {
@ -566,19 +566,19 @@ export class Augmentation {
return { moneyCost, repCost };
}
getLevel(player: IPlayer): number {
getLevel(): number {
// Get current Neuroflux level based on Player's augmentations
if (this.name === AugmentationNames.NeuroFluxGovernor) {
let currLevel = 0;
for (let i = 0; i < player.augmentations.length; ++i) {
if (player.augmentations[i].name === AugmentationNames.NeuroFluxGovernor) {
currLevel = player.augmentations[i].level;
for (let i = 0; i < Player.augmentations.length; ++i) {
if (Player.augmentations[i].name === AugmentationNames.NeuroFluxGovernor) {
currLevel = Player.augmentations[i].level;
}
}
// Account for purchased but uninstalled Augmentations
for (let i = 0; i < player.queuedAugmentations.length; ++i) {
if (player.queuedAugmentations[i].name == AugmentationNames.NeuroFluxGovernor) {
for (let i = 0; i < Player.queuedAugmentations.length; ++i) {
if (Player.queuedAugmentations[i].name == AugmentationNames.NeuroFluxGovernor) {
++currLevel;
}
}

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

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

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

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

@ -12,14 +12,13 @@ import Tooltip from "@mui/material/Tooltip";
import React, { useState } from "react";
import { OwnedAugmentationsOrderSetting } from "../../Settings/SettingEnums";
import { Settings } from "../../Settings/Settings";
import { use } from "../../ui/Context";
import { Player } from "../../Player";
import { StaticAugmentations } from "../StaticAugmentations";
import { AugmentationNames } from "../data/AugmentationNames";
export function InstalledAugmentations(): React.ReactElement {
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]);

@ -4,7 +4,7 @@
import { DoubleArrow } from "@mui/icons-material";
import { List, ListItem, ListItemText, Paper, Typography } from "@mui/material";
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 { Player } from "../../Player";
import { Settings } from "../../Settings/Settings";
@ -15,7 +15,7 @@ function calculateAugmentedStats(): Multipliers {
let augP: Multipliers = defaultMultipliers();
for (const aug of Player.queuedAugmentations) {
const augObj = StaticAugmentations[aug.name];
augP = mergeAugmentation(augP, augObj.mults);
augP = mergeMultipliers(augP, augObj.mults);
}
return augP;
}

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

@ -6,7 +6,7 @@ import { purchaseAugmentation } from "../../Faction/FactionHelpers";
import { isRepeatableAug } from "../AugmentationHelpers";
import { Money } from "../../ui/React/Money";
import { Modal } from "../../ui/React/Modal";
import { use } from "../../ui/Context";
import { Player } from "../../Player";
import Typography from "@mui/material/Typography";
import Button from "@mui/material/Button";
@ -18,14 +18,12 @@ interface IProps {
}
export function PurchaseAugmentationModal(props: IProps): React.ReactElement {
if (typeof props.aug === "undefined" || typeof props.faction === "undefined") {
if (!props.aug || !props.faction) {
return <></>;
}
const player = use.Player();
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;
}
@ -44,7 +42,7 @@ export function PurchaseAugmentationModal(props: IProps): React.ReactElement {
<br />
<br />
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 />
</Typography>

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

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

@ -5,7 +5,7 @@ import { uniqueId } from "lodash";
import React from "react";
import { SpecialServers } from "../../Server/data/SpecialServers";
import { Settings } from "../../Settings/Settings";
import { use } from "../../ui/Context";
import { Player } from "../../Player";
import { StatsRow } from "../../ui/React/StatsRow";
import { defaultMultipliers, getBitNodeMultipliers } from "../BitNode";
import { IBitNodeMultipliers } from "../BitNodeMultipliers";
@ -33,13 +33,12 @@ export function BitnodeMultiplierDescription({ n, level }: IProps): React.ReactE
}
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 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,
// or if it's BN12, ∞
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 (
<Box sx={{ columnCount: 2, columnGap: 1, mb: -2 }}>
@ -277,8 +276,7 @@ function InfiltrationMults({ 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) {
const rows: IBNMultRows = {
@ -297,8 +295,7 @@ function BladeburnerMults({ 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 rows: IBNMultRows = {
@ -313,8 +310,7 @@ function StanekMults({ 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 = {
GangSoftcap: {
@ -328,8 +324,7 @@ function GangMults({ 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) {
const rows: IBNMultRows = {

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

@ -1,8 +1,7 @@
import React from "react";
import { enterBitNode } from "../../RedPill";
import { BitNodes } from "../BitNode";
import { IRouter } from "../../ui/Router";
import { use } from "../../ui/Context";
import { Modal } from "../../ui/React/Modal";
import Typography from "@mui/material/Typography";
import Button from "@mui/material/Button";
@ -15,11 +14,9 @@ interface IProps {
level: number;
destroyedBitNode: number;
flume: boolean;
enter: (router: IRouter, flume: boolean, destroyedBitNode: number, newBitNode: number) => void;
}
export function PortalModal(props: IProps): React.ReactElement {
const router = use.Router();
const bitNodeKey = "BitNode" + props.n;
const bitNode = BitNodes[bitNodeKey];
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()}`}
autoFocus={true}
onClick={() => {
props.enter(router, props.flume, props.destroyedBitNode, props.n);
enterBitNode(props.flume, props.destroyedBitNode, props.n);
props.onClose();
}}
>

@ -3,9 +3,12 @@ import { getRandomInt } from "../utils/helpers/getRandomInt";
import { addOffset } from "../utils/helpers/addOffset";
import { Generic_fromJSON, Generic_toJSON, IReviverValue, Reviver } from "../utils/JSONReviver";
import { BladeburnerConstants } from "./data/Constants";
import { IBladeburner } from "./IBladeburner";
import { IAction, ISuccessChanceParams } from "./IAction";
import { IPerson } from "../PersonObjects/IPerson";
import { Bladeburner } from "./Bladeburner";
import { Person } from "../PersonObjects/Person";
interface ISuccessChanceParams {
est: boolean;
}
class StatsMultiplier {
[key: string]: number;
@ -41,7 +44,7 @@ export interface IActionParams {
teamCount?: number;
}
export class Action implements IAction {
export class Action {
name = "";
// 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
* @param inst {Bladeburner} - Bladeburner instance
*/
attempt(inst: IBladeburner, person: IPerson): boolean {
attempt(inst: Bladeburner, person: Person): boolean {
return Math.random() < this.getSuccessChance(inst, person);
}
@ -162,7 +165,7 @@ export class Action implements IAction {
return 1;
}
getActionTime(inst: IBladeburner, person: IPerson): number {
getActionTime(inst: Bladeburner, person: Person): number {
const difficulty = this.getDifficulty();
let baseTime = difficulty / BladeburnerConstants.DifficultyToTimeFactor;
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.
// eslint-disable-next-line @typescript-eslint/no-unused-vars
getTeamSuccessBonus(inst: IBladeburner): number {
getTeamSuccessBonus(inst: Bladeburner): number {
return 1;
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
getActionTypeSkillSuccessBonus(inst: IBladeburner): number {
getActionTypeSkillSuccessBonus(inst: Bladeburner): number {
return 1;
}
getChaosCompetencePenalty(inst: IBladeburner, params: ISuccessChanceParams): number {
getChaosCompetencePenalty(inst: Bladeburner, params: ISuccessChanceParams): number {
const city = inst.getCurrentCity();
if (params.est) {
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();
if (city.chaos > BladeburnerConstants.ChaosThreshold) {
const diff = 1 + (city.chaos - BladeburnerConstants.ChaosThreshold);
@ -212,7 +215,7 @@ export class Action implements IAction {
return 1;
}
getEstSuccessChance(inst: IBladeburner, person: IPerson): [number, number] {
getEstSuccessChance(inst: Bladeburner, person: Person): [number, number] {
function clamp(x: number): number {
return Math.max(0, Math.min(x, 1));
}
@ -233,7 +236,7 @@ export class Action implements IAction {
* @params - options:
* 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) {
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";
interface IParams {
@ -6,7 +5,7 @@ interface IParams {
type?: number;
}
export class ActionIdentifier implements IActionIdentifier {
export class ActionIdentifier {
name = "";
type = -1;

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

@ -1,6 +1,4 @@
import { Reviver, Generic_toJSON, Generic_fromJSON, IReviverValue } from "../utils/JSONReviver";
import { IBladeburner } from "./IBladeburner";
import { IActionIdentifier } from "./IActionIdentifier";
import { ActionIdentifier } from "./ActionIdentifier";
import { ActionTypes } from "./data/ActionTypes";
import { Growths } from "./data/Growths";
@ -13,11 +11,11 @@ import { formatNumber } from "../utils/StringHelperFunctions";
import { Skills } from "./Skills";
import { Skill } from "./Skill";
import { City } from "./City";
import { IAction } from "./IAction";
import { IPlayer } from "../PersonObjects/IPlayer";
import { Action } from "./Action";
import { Player } from "../Player";
import { createTaskTracker, ITaskTracker } from "../PersonObjects/ITaskTracker";
import { IPerson } from "../PersonObjects/IPerson";
import { IRouter } from "../ui/Router";
import { Person } from "../PersonObjects/Person";
import { Router } from "../ui/GameRoot";
import { ConsoleHelpText } from "./data/Help";
import { exceptionAlert } from "../utils/helpers/exceptionAlert";
import { getRandomInt } from "../utils/helpers/getRandomInt";
@ -25,7 +23,6 @@ import { BladeburnerConstants } from "./data/Constants";
import { numeralWrapper } from "../ui/numeralFormat";
import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers";
import { addOffset } from "../utils/helpers/addOffset";
import { Faction } from "../Faction/Faction";
import { Factions, factionExists } from "../Faction/Factions";
import { calculateHospitalizationCost } from "../Hospital/Hospital";
import { dialogBoxCreate } from "../ui/React/DialogBox";
@ -39,13 +36,13 @@ import { KEY } from "../utils/helpers/keyCodes";
import { isSleeveInfiltrateWork } from "../PersonObjects/Sleeve/Work/SleeveInfiltrateWork";
import { isSleeveSupportWork } from "../PersonObjects/Sleeve/Work/SleeveSupportWork";
interface BlackOpsAttempt {
export interface BlackOpsAttempt {
error?: string;
isAvailable?: boolean;
action?: BlackOperation;
}
export class Bladeburner implements IBladeburner {
export class Bladeburner {
numHosp = 0;
moneyLost = 0;
rank = 0;
@ -67,7 +64,7 @@ export class Bladeburner implements IBladeburner {
actionTimeCurrent = 0;
actionTimeOverflow = 0;
action: IActionIdentifier = new ActionIdentifier({
action: ActionIdentifier = new ActionIdentifier({
type: ActionTypes["Idle"],
});
@ -89,18 +86,18 @@ export class Bladeburner implements IBladeburner {
events: true,
};
automateEnabled = false;
automateActionHigh: IActionIdentifier = new ActionIdentifier({
automateActionHigh: ActionIdentifier = new ActionIdentifier({
type: ActionTypes["Idle"],
});
automateThreshHigh = 0;
automateActionLow: IActionIdentifier = new ActionIdentifier({
automateActionLow: ActionIdentifier = new ActionIdentifier({
type: ActionTypes["Idle"],
});
automateThreshLow = 0;
consoleHistory: string[] = [];
consoleLogs: string[] = ["Bladeburner Console", "Type 'help' to see console commands"];
constructor(player?: IPlayer) {
constructor() {
for (let i = 0; i < BladeburnerConstants.CityNames.length; ++i) {
this.cities[BladeburnerConstants.CityNames[i]] = new City(BladeburnerConstants.CityNames[i]);
}
@ -108,16 +105,14 @@ export class Bladeburner implements IBladeburner {
this.updateSkillMultipliers(); // Calls resetSkillMultipliers()
// Max Stamina is based on stats and Bladeburner-specific bonuses
if (player) this.calculateMaxStamina(player);
this.calculateMaxStamina();
this.stamina = this.maxStamina;
this.create();
}
getCurrentCity(): City {
const city = this.cities[this.city];
if (!(city instanceof City)) {
throw new Error("Bladeburner.getCurrentCity() did not properly return a City object");
}
if (!city) throw new Error("Invalid city in Bladeburner.getCurrentCity()");
return city;
}
@ -125,7 +120,7 @@ export class Bladeburner implements IBladeburner {
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
if (this.blackops[actionId.name] != null) {
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 };
}
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;
this.action = actionId;
this.actionTimeCurrent = 0;
@ -179,7 +175,7 @@ export class Bladeburner implements IBladeburner {
if (action.count < 1) {
return this.resetAction();
}
this.actionTimeToComplete = action.getActionTime(this, person);
this.actionTimeToComplete = action.getActionTime(this, Player);
} catch (e: unknown) {
exceptionAlert(e);
}
@ -196,7 +192,7 @@ export class Bladeburner implements IBladeburner {
if (actionId.name === "Raid" && this.getCurrentCity().comms === 0) {
return this.resetAction();
}
this.actionTimeToComplete = action.getActionTime(this, person);
this.actionTimeToComplete = action.getActionTime(this, Player);
} catch (e: unknown) {
exceptionAlert(e);
}
@ -214,14 +210,14 @@ export class Bladeburner implements IBladeburner {
if (testBlackOp.action === undefined) {
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) {
exceptionAlert(e);
}
break;
}
case ActionTypes["Recruitment"]:
this.actionTimeToComplete = this.getRecruitmentTime(person);
this.actionTimeToComplete = this.getRecruitmentTime(Player);
break;
case ActionTypes["Training"]:
case ActionTypes["FieldAnalysis"]:
@ -234,7 +230,7 @@ export class Bladeburner implements IBladeburner {
this.actionTimeToComplete = 60;
break;
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();
}
executeConsoleCommands(player: IPlayer, commands: string): void {
executeConsoleCommands(commands: string): void {
try {
// Console History
if (this.consoleHistory[this.consoleHistory.length - 1] != commands) {
@ -264,7 +260,7 @@ export class Bladeburner implements IBladeburner {
const arrayOfCommands = commands.split(";");
for (let i = 0; i < arrayOfCommands.length; ++i) {
this.executeConsoleCommand(player, arrayOfCommands[i]);
this.executeConsoleCommand(arrayOfCommands[i]);
}
} catch (e: unknown) {
exceptionAlert(e);
@ -309,7 +305,7 @@ export class Bladeburner implements IBladeburner {
}
// working on
getActionIdFromTypeAndName(type = "", name = ""): IActionIdentifier | null {
getActionIdFromTypeAndName(type = "", name = ""): ActionIdentifier | null {
if (type === "" || name === "") {
return null;
}
@ -394,7 +390,7 @@ export class Bladeburner implements IBladeburner {
return null;
}
executeStartConsoleCommand(player: IPlayer, args: string[]): void {
executeStartConsoleCommand(args: string[]): void {
if (args.length !== 3) {
this.postToConsole("Invalid usage of 'start' console command: start [type] [name]");
this.postToConsole("Use 'help start' for more info");
@ -407,7 +403,7 @@ export class Bladeburner implements IBladeburner {
if (GeneralActions[name] != null) {
this.action.type = ActionTypes[name];
this.action.name = name;
this.startAction(player, this.action);
this.startAction(this.action);
} else {
this.postToConsole("Invalid action name specified: " + args[2]);
}
@ -417,7 +413,7 @@ export class Bladeburner implements IBladeburner {
if (this.contracts[name] != null) {
this.action.type = ActionTypes.Contract;
this.action.name = name;
this.startAction(player, this.action);
this.startAction(this.action);
} else {
this.postToConsole("Invalid contract name specified: " + args[2]);
}
@ -429,7 +425,7 @@ export class Bladeburner implements IBladeburner {
if (this.operations[name] != null) {
this.action.type = ActionTypes.Operation;
this.action.name = name;
this.startAction(player, this.action);
this.startAction(this.action);
} else {
this.postToConsole("Invalid Operation name specified: " + args[2]);
}
@ -441,7 +437,7 @@ export class Bladeburner implements IBladeburner {
if (BlackOperations[name] != null) {
this.action.type = ActionTypes.BlackOperation;
this.action.name = name;
this.startAction(player, this.action);
this.startAction(this.action);
} else {
this.postToConsole("Invalid BlackOp name specified: " + args[2]);
}
@ -542,7 +538,7 @@ export class Bladeburner implements IBladeburner {
case 3: {
const skillName = args[2];
const skill = Skills[skillName];
if (skill == null || !(skill instanceof Skill)) {
if (!skill) {
this.postToConsole("Invalid skill name (Note that it is case-sensitive): " + skillName);
break;
}
@ -684,10 +680,7 @@ export class Bladeburner implements IBladeburner {
".",
);
} else if (flag.toLowerCase().includes("en")) {
if (
!(this.automateActionLow instanceof ActionIdentifier) ||
!(this.automateActionHigh instanceof ActionIdentifier)
) {
if (!this.automateActionLow || !this.automateActionHigh) {
return this.log("Failed to enable automation. Actions were not set");
}
this.automateEnabled = true;
@ -820,7 +813,7 @@ export class Bladeburner implements IBladeburner {
return args;
}
executeConsoleCommand(player: IPlayer, command: string): void {
executeConsoleCommand(command: string): void {
command = command.trim();
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);
break;
case "start":
this.executeStartConsoleCommand(player, args);
this.executeStartConsoleCommand(args);
break;
case "stop":
this.resetAction();
@ -898,9 +891,7 @@ export class Bladeburner implements IBladeburner {
// Choose random source/destination city for events
const sourceCityName = BladeburnerConstants.CityNames[getRandomInt(0, 5)];
const sourceCity = this.cities[sourceCityName];
if (!(sourceCity instanceof City)) {
throw new Error("sourceCity was not a City object in Bladeburner.randomEvent()");
}
if (!sourceCity) throw new Error("Invalid sourceCity in Bladeburner.randomEvent()");
let destCityName = BladeburnerConstants.CityNames[getRandomInt(0, 5)];
while (destCityName === sourceCityName) {
@ -908,9 +899,7 @@ export class Bladeburner implements IBladeburner {
}
const destCity = this.cities[destCityName];
if (!(sourceCity instanceof City) || !(destCity instanceof City)) {
throw new Error("sourceCity/destCity was not a City object in Bladeburner.randomEvent()");
}
if (!sourceCity || !destCity) throw new Error("Invalid sourceCity or destCity in Bladeburner.randomEvent()");
if (chance <= 0.05) {
// New Synthoid Community, 5%
@ -994,7 +983,7 @@ export class Bladeburner implements IBladeburner {
* @param action(Action obj) - Derived action class
* @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();
/**
@ -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)
const CharismaLinearFactor = 1e3;
const CharismaExponentialFactor = 0.045;
@ -1034,11 +1023,11 @@ export class Bladeburner implements IBladeburner {
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);
}
getRecruitmentTime(person: IPerson): number {
getRecruitmentTime(person: Person): number {
const effCharisma = person.skills.charisma * this.skillMultipliers.effCha;
const charismaFactor = Math.pow(effCharisma, 0.81) + effCharisma / 90;
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) {
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);
this.teamSize -= losses;
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--) {
const r = Math.floor(Math.random() * sup.length);
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
* 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) {
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();
switch (actionIdent.type) {
case ActionTypes["Contract"]:
@ -1304,24 +1293,16 @@ export class Bladeburner implements IBladeburner {
this.changeRank(person, gain);
if (isOperation && this.logging.ops) {
this.log(
`${person.whoAmI()}: ` +
action.name +
" successfully completed! Gained " +
formatNumber(gain, 3) +
" rank",
`${person.whoAmI()}: ${action.name} successfully completed! Gained ${formatNumber(gain, 3)} rank`,
);
} else if (!isOperation && this.logging.contracts) {
this.log(
`${person.whoAmI()}: ` +
action.name +
" contract successfully completed! Gained " +
formatNumber(gain, 3) +
" rank and " +
numeralWrapper.formatMoney(moneyGain),
`${person.whoAmI()}: ${action.name} 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 {
retValue = this.getActionStats(action, person, false);
++action.failures;
@ -1335,7 +1316,7 @@ export class Bladeburner implements IBladeburner {
damage = action.hpLoss * difficultyMultiplier;
damage = Math.ceil(addOffset(damage, 10));
this.hpLost += damage;
const cost = calculateHospitalizationCost(player, damage);
const cost = calculateHospitalizationCost(damage);
if (person.takeDamage(damage)) {
++this.numHosp;
this.moneyLost += cost;
@ -1353,7 +1334,7 @@ export class Bladeburner implements IBladeburner {
} else if (!isOperation && this.logging.contracts) {
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) {
action.level = action.maxLevel;
@ -1412,7 +1393,7 @@ export class Bladeburner implements IBladeburner {
if (action.hpLoss) {
damage = action.hpLoss * difficultyMultiplier;
damage = Math.ceil(addOffset(damage, 10));
const cost = calculateHospitalizationCost(player, damage);
const cost = calculateHospitalizationCost(damage);
if (person.takeDamage(damage)) {
++this.numHosp;
this.moneyLost += cost;
@ -1440,7 +1421,7 @@ export class Bladeburner implements IBladeburner {
const losses = getRandomInt(1, teamLossMax);
this.teamSize -= losses;
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--) {
const r = Math.floor(Math.random() * sup.length);
sup[r].takeDamage(sup[r].hp.max);
@ -1603,8 +1584,8 @@ export class Bladeburner implements IBladeburner {
return retValue;
}
infiltrateSynthoidCommunities(p: IPlayer): void {
const infilSleeves = p.sleeves.filter((s) => isSleeveInfiltrateWork(s.currentWork)).length;
infiltrateSynthoidCommunities(): void {
const infilSleeves = Player.sleeves.filter((s) => isSleeveInfiltrateWork(s.currentWork)).length;
const amt = Math.pow(infilSleeves, -0.5) / 2;
for (const contract of Object.keys(this.contracts)) {
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)) {
throw new Error("NaN passed into Bladeburner.changeRank()");
}
@ -1630,7 +1611,7 @@ export class Bladeburner implements IBladeburner {
const bladeburnersFactionName = FactionNames.Bladeburners;
if (factionExists(bladeburnersFactionName)) {
const bladeburnerFac = Factions[bladeburnersFactionName];
if (!(bladeburnerFac instanceof Faction)) {
if (!bladeburnerFac) {
throw new Error(
`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.actionTimeToComplete <= 0) {
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");
}
@ -1670,31 +1651,31 @@ export class Bladeburner implements IBladeburner {
if (this.actionTimeCurrent >= this.actionTimeToComplete) {
this.actionTimeOverflow = this.actionTimeCurrent - this.actionTimeToComplete;
const action = this.getActionObject(this.action);
const retValue = this.completeAction(player, player, this.action);
player.gainMoney(retValue.money, "bladeburner");
player.gainStats(retValue);
const retValue = this.completeAction(Player, this.action);
Player.gainMoney(retValue.money, "bladeburner");
Player.gainStats(retValue);
// Operation Daedalus
if (action == null) {
throw new Error("Failed to get BlackOperation Object for: " + this.action.name);
} 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 {
const effAgility = player.skills.agility * this.skillMultipliers.effAgi;
calculateStaminaGainPerSecond(): number {
const effAgility = Player.skills.agility * this.skillMultipliers.effAgi;
const maxStaminaBonus = this.maxStamina / BladeburnerConstants.MaxStaminaToGainFactor;
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 {
const effAgility = player.skills.agility * this.skillMultipliers.effAgi;
calculateMaxStamina(): void {
const effAgility = Player.skills.agility * this.skillMultipliers.effAgi;
const maxStamina =
(Math.pow(effAgility, 0.8) + this.staminaBonus) *
this.skillMultipliers.stamina *
player.mults.bladeburner_max_stamina;
Player.mults.bladeburner_max_stamina;
if (this.maxStamina !== maxStamina) {
const oldMax = this.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.
if (!router.isInitialized) return;
if (!Router.isInitialized) return;
// 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"]) {
let msg = "Your Bladeburner action was cancelled because you started doing something else.";
if (this.automateEnabled) {
@ -2006,8 +1987,8 @@ export class Bladeburner implements IBladeburner {
this.storedCycles -= seconds * BladeburnerConstants.CyclesPerSecond;
// Stamina
this.calculateMaxStamina(player);
this.stamina += this.calculateStaminaGainPerSecond(player) * seconds;
this.calculateMaxStamina();
this.stamina += this.calculateStaminaGainPerSecond() * seconds;
this.stamina = Math.min(this.maxStamina, this.stamina);
// Count increase for contracts/operations
@ -2027,9 +2008,7 @@ export class Bladeburner implements IBladeburner {
// Chaos goes down very slowly
for (const cityName of BladeburnerConstants.CityNames) {
const city = this.cities[cityName];
if (!(city instanceof City)) {
throw new Error("Invalid City object when processing passive chaos reduction in Bladeburner.process");
}
if (!city) throw new Error("Invalid city when processing passive chaos reduction in Bladeburner.process");
city.chaos -= 0.0001 * seconds;
city.chaos = Math.max(0, city.chaos);
}
@ -2042,7 +2021,7 @@ export class Bladeburner implements IBladeburner {
this.randomEventCounter += getRandomInt(240, 600);
}
this.processAction(router, player, seconds);
this.processAction(seconds);
// Automation
if (this.automateEnabled) {
@ -2053,7 +2032,7 @@ export class Bladeburner implements IBladeburner {
type: this.automateActionLow.type,
name: this.automateActionLow.name,
});
this.startAction(player, this.action);
this.startAction(this.action);
}
} else if (this.stamina >= this.automateThreshHigh) {
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,
name: this.automateActionHigh.name,
});
this.startAction(player, this.action);
this.startAction(this.action);
}
}
}
}
}
getTypeAndNameFromActionId(actionId: IActionIdentifier): {
getTypeAndNameFromActionId(actionId: ActionIdentifier): {
type: string;
name: string;
} {
@ -2121,7 +2100,7 @@ export class Bladeburner implements IBladeburner {
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 actionId = this.getActionIdFromTypeAndName(type, name);
if (actionId == null) {
@ -2139,7 +2118,7 @@ export class Bladeburner implements IBladeburner {
}
try {
this.startAction(player, actionId);
this.startAction(actionId);
workerScript.log(
"bladeburner.startAction",
() => `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);
if (actionId == null) {
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);
if (actionId == null) {
return "bladeburner.getActionEstimatedSuccessChance";

@ -1,4 +1,4 @@
import { IBladeburner } from "./IBladeburner";
import { Bladeburner } from "./Bladeburner";
import { Action, IActionParams } from "./Action";
import { Generic_fromJSON, Generic_toJSON, IReviverValue, Reviver } from "../utils/JSONReviver";
@ -7,7 +7,7 @@ export class Contract extends Action {
super(params);
}
getActionTypeSkillSuccessBonus(inst: IBladeburner): number {
getActionTypeSkillSuccessBonus(inst: Bladeburner): number {
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 { Action, IActionParams } from "./Action";
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.
getTeamSuccessBonus(inst: IBladeburner): number {
getTeamSuccessBonus(inst: Bladeburner): number {
if (this.teamCount && this.teamCount > 0) {
this.teamCount = Math.min(this.teamCount, inst.teamSize);
const teamMultiplier = Math.pow(this.teamCount, 0.05);
@ -29,11 +29,11 @@ export class Operation extends Action {
return 1;
}
getActionTypeSkillSuccessBonus(inst: IBladeburner): number {
getActionTypeSkillSuccessBonus(inst: Bladeburner): number {
return inst.skillMultipliers.successChanceOperation;
}
getChaosDifficultyBonus(inst: IBladeburner /*, params: ISuccessChanceParams*/): number {
getChaosDifficultyBonus(inst: Bladeburner /*, params: ISuccessChanceParams*/): number {
const city = inst.getCurrentCity();
if (city.chaos > BladeburnerConstants.ChaosThreshold) {
const diff = 1 + (city.chaos - BladeburnerConstants.ChaosThreshold);

@ -1,33 +1,5 @@
import { CityName } from "./../../Locations/data/CityNames";
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;
} = {
export const BladeburnerConstants = {
CityNames: [
CityName.Aevum,
CityName.Chongqing,

@ -1,19 +1,4 @@
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;
} = {
export const SkillNames = {
BladesIntuition: "Blade's Intuition",
Cloak: "Cloak",
Marksman: "Marksman",

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

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

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

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

@ -2,12 +2,10 @@ import React from "react";
import { BlackOperations } from "../BlackOperations";
import { BlackOperation } from "../BlackOperation";
import { BlackOpElem } from "./BlackOpElem";
import { IBladeburner } from "../IBladeburner";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { Bladeburner } from "../Bladeburner";
interface IProps {
bladeburner: IBladeburner;
player: IPlayer;
bladeburner: Bladeburner;
}
export function BlackOpList(props: IProps): React.ReactElement {
@ -35,7 +33,7 @@ export function BlackOpList(props: IProps): React.ReactElement {
return (
<>
{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 { BlackOpList } from "./BlackOpList";
import { IBladeburner } from "../IBladeburner";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { Bladeburner } from "../Bladeburner";
import Typography from "@mui/material/Typography";
import { FactionNames } from "../../Faction/data/FactionNames";
import { use } from "../../ui/Context";
import { Router } from "../../ui/GameRoot";
import { BlackOperationNames } from "../data/BlackOperationNames";
import { Button } from "@mui/material";
import { CorruptableText } from "../../ui/React/CorruptableText";
interface IProps {
bladeburner: IBladeburner;
player: IPlayer;
bladeburner: Bladeburner;
}
export function BlackOpPage(props: IProps): React.ReactElement {
const router = use.Router();
return (
<>
<Typography>
@ -33,11 +30,11 @@ export function BlackOpPage(props: IProps): React.ReactElement {
losses.
</Typography>
{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>
</Button>
) : (
<BlackOpList bladeburner={props.bladeburner} player={props.player} />
<BlackOpList bladeburner={props.bladeburner} />
)}
</>
);

@ -1,11 +1,10 @@
import React from "react";
import { FactionNames } from "../../Faction/data/FactionNames";
import { use } from "../../ui/Context";
import { Router } from "../../ui/GameRoot";
import { CinematicText } from "../../ui/React/CinematicText";
import { dialogBoxCreate } from "../../ui/React/DialogBox";
export function BladeburnerCinematic(): React.ReactElement {
const router = use.Router();
return (
<CinematicText
lines={[
@ -32,7 +31,7 @@ export function BladeburnerCinematic(): React.ReactElement {
"investigating and dealing with Synthoid threats.",
]}
onDone={() => {
router.toTerminal();
Router.toTerminal();
dialogBoxCreate(
`Visit the National Security Agency (NSA) to apply for their ${FactionNames.Bladeburners} ` +
"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 { AllPages } from "./AllPages";
import { use } from "../../ui/Context";
import { Player } from "../../Player";
import Box from "@mui/material/Box";
export function BladeburnerRoot(): React.ReactElement {
const player = use.Player();
const router = use.Router();
const setRerender = useState(false)[1];
function rerender(): void {
setRerender((old) => !old);
@ -19,16 +17,16 @@ export function BladeburnerRoot(): React.ReactElement {
return () => clearInterval(id);
}, []);
const bladeburner = player.bladeburner;
if (bladeburner === null) return <></>;
const bladeburner = Player.bladeburner;
if (!bladeburner) return <></>;
return (
<Box display="flex" flexDirection="column">
<Box sx={{ display: "grid", gridTemplateColumns: "4fr 8fr", p: 1 }}>
<Stats bladeburner={bladeburner} player={player} router={router} />
<Console bladeburner={bladeburner} player={player} />
<Stats bladeburner={bladeburner} />
<Console bladeburner={bladeburner} />
</Box>
<AllPages bladeburner={bladeburner} player={player} />
<AllPages bladeburner={bladeburner} />
</Box>
);
}

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

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

@ -1,11 +1,9 @@
import React from "react";
import { ContractElem } from "./ContractElem";
import { IBladeburner } from "../IBladeburner";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { Bladeburner } from "../Bladeburner";
interface IProps {
bladeburner: IBladeburner;
player: IPlayer;
bladeburner: Bladeburner;
}
export function ContractList(props: IProps): React.ReactElement {
@ -14,7 +12,7 @@ export function ContractList(props: IProps): React.ReactElement {
return (
<>
{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 { ContractList } from "./ContractList";
import { IBladeburner } from "../IBladeburner";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { Bladeburner } from "../Bladeburner";
import Typography from "@mui/material/Typography";
interface IProps {
bladeburner: IBladeburner;
player: IPlayer;
bladeburner: Bladeburner;
}
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
difficult, but grant more rank, experience, and money.
</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 { createProgressBarText } from "../../utils/helpers/createProgressBarText";
import { formatNumber, convertTimeMsToTimeElapsedString } from "../../utils/StringHelperFunctions";
import { IBladeburner } from "../IBladeburner";
import { IAction } from "../IAction";
import { Bladeburner } from "../Bladeburner";
import { Action } from "../Action";
import { GeneralActions } from "../data/GeneralActions";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { Player } from "../../Player";
import { CopyableText } from "../../ui/React/CopyableText";
import { StartButton } from "./StartButton";
@ -15,9 +15,8 @@ import Box from "@mui/material/Box";
import Paper from "@mui/material/Paper";
interface IProps {
bladeburner: IBladeburner;
player: IPlayer;
action: IAction;
bladeburner: Bladeburner;
action: Action;
}
export function GeneralActionElem(props: IProps): React.ReactElement {
@ -40,13 +39,13 @@ export function GeneralActionElem(props: IProps): React.ReactElement {
case "Incite Violence":
return 60;
case "Recruitment":
return props.bladeburner.getRecruitmentTime(props.player);
return props.bladeburner.getRecruitmentTime(Player);
}
return -1; // dead code
})();
const successChance =
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;
const actionData = GeneralActions[props.action.name];

@ -2,12 +2,10 @@ import React from "react";
import { GeneralActionElem } from "./GeneralActionElem";
import { Action } from "../Action";
import { GeneralActions } from "../GeneralActions";
import { IBladeburner } from "../IBladeburner";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { Bladeburner } from "../Bladeburner";
interface IProps {
bladeburner: IBladeburner;
player: IPlayer;
bladeburner: Bladeburner;
}
export function GeneralActionList(props: IProps): React.ReactElement {
@ -20,7 +18,7 @@ export function GeneralActionList(props: IProps): React.ReactElement {
return (
<>
{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 { GeneralActionList } from "./GeneralActionList";
import { IBladeburner } from "../IBladeburner";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { Bladeburner } from "../Bladeburner";
import Typography from "@mui/material/Typography";
interface IProps {
bladeburner: IBladeburner;
player: IPlayer;
bladeburner: Bladeburner;
}
export function GeneralActionPage(props: IProps): React.ReactElement {
return (
<>
<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 { StartButton } from "./StartButton";
import { TeamSizeButton } from "./TeamSizeButton";
import { IBladeburner } from "../IBladeburner";
import { Bladeburner } from "../Bladeburner";
import { Operation } from "../Operation";
import { Operations } from "../data/Operations";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { Player } from "../../Player";
import { CopyableText } from "../../ui/React/CopyableText";
import Typography from "@mui/material/Typography";
import Paper from "@mui/material/Paper";
interface IProps {
bladeburner: IBladeburner;
player: IPlayer;
bladeburner: Bladeburner;
action: Operation;
}
@ -33,7 +32,7 @@ export function OperationElem(props: IProps): React.ReactElement {
props.bladeburner.actionTimeCurrent + props.bladeburner.actionTimeOverflow,
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];
if (actionData === undefined) {

@ -1,11 +1,9 @@
import React from "react";
import { OperationElem } from "./OperationElem";
import { IBladeburner } from "../IBladeburner";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { Bladeburner } from "../Bladeburner";
interface IProps {
bladeburner: IBladeburner;
player: IPlayer;
bladeburner: Bladeburner;
}
export function OperationList(props: IProps): React.ReactElement {
@ -14,7 +12,7 @@ export function OperationList(props: IProps): React.ReactElement {
return (
<>
{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 { OperationList } from "./OperationList";
import { IBladeburner } from "../IBladeburner";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { Bladeburner } from "../Bladeburner";
import Typography from "@mui/material/Typography";
interface IProps {
bladeburner: IBladeburner;
player: IPlayer;
bladeburner: Bladeburner;
}
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
difficult, but grant more rank and experience.
</Typography>
<OperationList bladeburner={props.bladeburner} player={props.player} />
<OperationList bladeburner={props.bladeburner} />
</>
);
}

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

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

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

@ -1,20 +1,20 @@
import React from "react";
import { IBladeburner } from "../IBladeburner";
import { Bladeburner } from "../Bladeburner";
import { BlackOperation } from "../BlackOperation";
import { use } from "../../ui/Context";
import { Player } from "../../Player";
import Button from "@mui/material/Button";
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
import { ActionIdentifier } from "../ActionIdentifier";
interface IProps {
bladeburner: IBladeburner;
bladeburner: Bladeburner;
type: number;
name: string;
rerender: () => void;
}
export function StartButton(props: IProps): React.ReactElement {
const player = use.Player();
const action = props.bladeburner.getActionObject({ name: props.name, type: props.type });
const action = props.bladeburner.getActionObject(new ActionIdentifier({ name: props.name, type: props.type }));
if (action == null) {
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;
props.bladeburner.action.type = props.type;
props.bladeburner.action.name = props.name;
if (!player.hasAugmentation(AugmentationNames.BladesSimulacrum, true)) player.finishWork(true);
props.bladeburner.startAction(player, props.bladeburner.action);
if (!Player.hasAugmentation(AugmentationNames.BladesSimulacrum, true)) Player.finishWork(true);
props.bladeburner.startAction(props.bladeburner.action);
props.rerender();
}

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

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

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

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

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

@ -1,8 +1,8 @@
import * as React from "react";
import { IPlayer } from "../PersonObjects/IPlayer";
import { Player } from "../Player";
import { Money } from "../ui/React/Money";
import { Game, reachedLimit } from "./Game";
import { win, reachedLimit } from "./Game";
import { Deck } from "./CardDeck/Deck";
import { Hand } from "./CardDeck/Hand";
import { InputAdornment } from "@mui/material";
@ -24,10 +24,6 @@ enum Result {
Tie = "Push! (Tie)",
}
type Props = {
p: IPlayer;
};
type State = {
playerHand: Hand;
dealerHand: Hand;
@ -40,11 +36,11 @@ type State = {
wagerInvalidHelperText: string;
};
export class Blackjack extends Game<Props, State> {
export class Blackjack extends React.Component<Record<string, never>, State> {
deck: Deck;
constructor(props: Props) {
super(props);
constructor() {
super({});
this.deck = new Deck(DECK_COUNT);
@ -64,20 +60,19 @@ export class Blackjack extends Game<Props, State> {
}
canStartGame = (): boolean => {
const { p } = this.props;
const { bet } = this.state;
return p.canAfford(bet);
return Player.canAfford(bet);
};
startGame = (): void => {
if (!this.canStartGame() || reachedLimit(this.props.p)) {
if (!this.canStartGame() || reachedLimit()) {
return;
}
// 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.
this.props.p.loseMoney(this.state.bet, "casino");
win(-this.state.bet);
const playerHand = 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}`);
})(); // This can't happen, right?
this.win(this.props.p, gains);
win(gains);
this.setState({
gameInProgress: false,
result,
@ -239,7 +234,6 @@ export class Blackjack extends Game<Props, State> {
};
wagerOnChange = (event: React.ChangeEvent<HTMLInputElement>): void => {
const { p } = this.props;
const betInput = event.target.value;
const wager = Math.round(parseFloat(betInput));
if (isNaN(wager)) {
@ -263,7 +257,7 @@ export class Blackjack extends Game<Props, State> {
wagerInvalid: true,
wagerInvalidHelperText: "Exceeds max bet",
});
} else if (!p.canAfford(wager)) {
} else if (!Player.canAfford(wager)) {
this.setState({
bet: 0,
betInput,

@ -5,7 +5,6 @@
*/
import React, { useState } from "react";
import { IPlayer } from "../PersonObjects/IPlayer";
import { BadRNG } from "./RNG";
import { win, reachedLimit } from "./Game";
import { trusted } from "./utils";
@ -15,14 +14,10 @@ import TextField from "@mui/material/TextField";
import Button from "@mui/material/Button";
import Box from "@mui/material/Box";
type IProps = {
p: IPlayer;
};
const minPlay = 0;
const maxPlay = 10e3;
export function CoinFlip(props: IProps): React.ReactElement {
export function CoinFlip(): React.ReactElement {
const [investment, setInvestment] = useState(1000);
const [result, setResult] = useState(<span> </span>);
const [status, setStatus] = useState("");
@ -43,7 +38,7 @@ export function CoinFlip(props: IProps): React.ReactElement {
}
function play(guess: string): void {
if (reachedLimit(props.p)) return;
if (reachedLimit()) return;
const v = BadRNG.random();
let letter: string;
if (v < 0.5) {
@ -65,11 +60,11 @@ export function CoinFlip(props: IProps): React.ReactElement {
setTimeout(() => setPlayLock(false), 250);
if (correct) {
win(props.p, investment);
win(investment);
} else {
win(props.p, -investment);
win(-investment);
}
if (reachedLimit(props.p)) return;
if (reachedLimit()) 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 { IPlayer } from "../PersonObjects/IPlayer";
import { Money } from "../ui/React/Money";
import { win, reachedLimit } from "./Game";
import { WHRNG } from "./RNG";
@ -9,10 +8,6 @@ import Typography from "@mui/material/Typography";
import Button from "@mui/material/Button";
import TextField from "@mui/material/TextField";
type IProps = {
p: IPlayer;
};
const minPlay = 0;
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 [investment, setInvestment] = useState(1000);
const [canPlay, setCanPlay] = useState(true);
@ -151,7 +146,7 @@ export function Roulette(props: IProps): React.ReactElement {
}
function play(strategy: Strategy): void {
if (reachedLimit(props.p)) return;
if (reachedLimit()) return;
setCanPlay(false);
setLock(false);
@ -184,14 +179,14 @@ export function Roulette(props: IProps): React.ReactElement {
</>
);
}
win(props.p, gain);
win(gain);
setCanPlay(true);
setLock(true);
setStatus(status);
setN(n);
reachedLimit(props.p);
reachedLimit();
}, 1600);
}

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

@ -191,7 +191,8 @@ function getRandomFilename(server: BaseServer, reward: ICodingContractReward): s
}
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;

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

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

@ -1,6 +1,6 @@
import React from "react";
import { Company } from "../Company";
import { use } from "../../ui/Context";
import { Player } from "../../Player";
import { Modal } from "../../ui/React/Modal";
import Typography from "@mui/material/Typography";
import Button from "@mui/material/Button";
@ -14,9 +14,8 @@ interface IProps {
}
export function QuitJobModal(props: IProps): React.ReactElement {
const player = use.Player();
function quit(): void {
player.quitJob(props.locName);
Player.quitJob(props.locName);
props.onQuit();
props.onClose();
}

@ -1,9 +1,6 @@
import { Player } from "../Player";
import { IPlayer } from "src/PersonObjects/IPlayer";
import { MaterialSizes } from "./MaterialSizes";
import { ICorporation } from "./ICorporation";
import { Corporation } from "./Corporation";
import { IIndustry } from "./IIndustry";
import { IndustryStartingCosts, IndustryResearchTrees } from "./IndustryData";
import { Industry } from "./Industry";
import { CorporationConstants } from "./data/Constants";
@ -18,7 +15,7 @@ import { EmployeePositions } from "./EmployeePositions";
import { ResearchMap } from "./ResearchMap";
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))
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) {
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) {
throw new Error("Insufficient funds");
}
@ -72,7 +69,7 @@ export function UnlockUpgrade(corporation: ICorporation, upgrade: CorporationUnl
corporation.unlock(upgrade);
}
export function LevelUpgrade(corporation: ICorporation, upgrade: CorporationUpgrade): void {
export function LevelUpgrade(corporation: Corporation, upgrade: CorporationUpgrade): void {
const baseCost = upgrade.basePrice;
const priceMult = upgrade.priceMult;
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) {
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;
}
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 maxAmount = (warehouse.size - warehouse.sizeUsed) / matSize;
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 (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!");
@ -287,20 +284,20 @@ export function SellShares(corporation: ICorporation, player: IPlayer, numShares
corporation.sharePrice = newSharePrice;
corporation.shareSalesUntilPriceUpdate = newSharesUntilUpdate;
corporation.shareSaleCooldown = CorporationConstants.SellSharesCooldown;
player.gainMoney(profit, "corporation");
Player.gainMoney(profit, "corporation");
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 (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 (!corporation.public) throw new Error("You haven't gone public!");
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.issuedShares -= numShares;
player.loseMoney(numShares * buybackPrice, "corporation");
Player.loseMoney(numShares * buybackPrice, "corporation");
return true;
}
@ -319,7 +316,7 @@ export function AutoAssignJob(office: OfficeSpace, job: string, count: number):
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 costMultiplier = 1.09;
// 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;
}
export function BuyCoffee(corp: ICorporation, office: OfficeSpace): boolean {
export function BuyCoffee(corp: Corporation, office: OfficeSpace): boolean {
const cost = office.getCoffeeCost();
if (corp.funds < cost) {
return false;
@ -347,7 +344,7 @@ export function BuyCoffee(corp: ICorporation, office: OfficeSpace): boolean {
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 cost = costPerEmployee * office.employees.length;
if (corp.funds < cost) {
@ -362,9 +359,9 @@ export function ThrowParty(corp: ICorporation, office: OfficeSpace, costPerEmplo
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 (division.warehouses[city] instanceof Warehouse) return;
if (division.warehouses[city]) return;
division.warehouses[city] = new Warehouse({
corp: corp,
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);
if (corp.funds < sizeUpgradeCost) return;
warehouse.level += amt;
@ -389,7 +386,7 @@ export function UpgradeWarehouse(corp: ICorporation, division: IIndustry, wareho
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();
if (corp.funds < cost) return;
corp.funds = corp.funds - cost;
@ -397,8 +394,8 @@ export function HireAdVert(corp: ICorporation, division: IIndustry): void {
}
export function MakeProduct(
corp: ICorporation,
division: IIndustry,
corp: Corporation,
division: Industry,
city: string,
productName: string,
designInvest: number,
@ -442,7 +439,7 @@ export function MakeProduct(
designCost: designInvest,
advCost: marketingInvest,
});
if (products[product.name] instanceof Product) {
if (products[product.name]) {
throw new Error(`You already have a product with this name!`);
}
@ -450,7 +447,7 @@ export function MakeProduct(
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];
if (researchTree === undefined) throw new Error(`No research tree for industry '${division.type}'`);
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) {
const city = CorporationConstants.Cities[i];
const warehouse = division.warehouses[city];
if (!(warehouse instanceof Warehouse)) {
if (!warehouse) {
continue;
}
if (Player.corporation instanceof Corporation) {
if (Player.corporation) {
// Stores cycles in a "buffer". Processed separately using Engine Counters
warehouse.updateSize(Player.corporation, division);
}

@ -1,14 +1,13 @@
import { CorporationState } from "./CorporationState";
import { CorporationUnlockUpgrade, CorporationUnlockUpgrades } from "./data/CorporationUnlockUpgrades";
import { CorporationUpgrade, CorporationUpgrades } from "./data/CorporationUpgrades";
import { Warehouse } from "./Warehouse";
import { CorporationConstants } from "./data/Constants";
import { Industry } from "./Industry";
import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers";
import { showLiterature } from "../Literature/LiteratureHelpers";
import { LiteratureNames } from "../Literature/data/LiteratureNames";
import { IPlayer } from "../PersonObjects/IPlayer";
import { Player } from "../Player";
import { dialogBoxCreate } from "../ui/React/DialogBox";
import { Reviver, Generic_toJSON, Generic_fromJSON, IReviverValue } from "../utils/JSONReviver";
@ -76,7 +75,7 @@ export class Corporation {
this.storedCycles += numCycles;
}
process(player: IPlayer): void {
process(): void {
if (this.storedCycles >= CorporationConstants.CyclesPerIndustryStateCycle) {
const state = this.getState();
const marketCycles = 1;
@ -120,7 +119,7 @@ export class Corporation {
if (isNaN(this.funds) || this.funds === Infinity || this.funds === -Infinity) {
dialogBoxCreate(
"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)",
);
this.funds = 150e9;
@ -139,7 +138,7 @@ export class Corporation {
} else {
const totalDividends = this.dividendRate * cycleProfit;
const retainedEarnings = cycleProfit - totalDividends;
player.gainMoney(this.getCycleDividends(), "corporation");
Player.gainMoney(this.getCycleDividends(), "corporation");
this.addFunds(retainedEarnings);
}
} else {
@ -331,7 +330,7 @@ export class Corporation {
for (const city of Object.keys(industry.warehouses)) {
const warehouse = industry.warehouses[city];
if (warehouse === 0) continue;
if (industry.warehouses.hasOwnProperty(city) && warehouse instanceof Warehouse) {
if (industry.warehouses.hasOwnProperty(city) && warehouse) {
warehouse.updateSize(this, industry);
}
}
@ -428,9 +427,9 @@ export class Corporation {
// 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 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
const homeComp = player.getHomeComputer();
const homeComp = Player.getHomeComputer();
let hasHandbook = false;
const handbookFn = LiteratureNames.CorporationManagementHandbook;
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 { Generic_fromJSON, Generic_toJSON, IReviverValue, Reviver } from "../utils/JSONReviver";
import { EmployeePositions } from "./EmployeePositions";
import { ICorporation } from "./ICorporation";
import { IIndustry } from "./IIndustry";
import { Corporation } from "./Corporation";
import { Industry } from "./Industry";
interface IParams {
name?: string;
@ -77,7 +77,7 @@ export class Employee {
return salary;
}
calculateProductivity(corporation: ICorporation, industry: IIndustry): number {
calculateProductivity(corporation: Corporation, industry: Industry): number {
const effCre = this.cre * corporation.getEmployeeCreMultiplier() * industry.getEmployeeCreMultiplier(),
effCha = this.cha * corporation.getEmployeeChaMultiplier() * industry.getEmployeeChaMultiplier(),
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 { MaterialSizes } from "./MaterialSizes";
import { Warehouse } from "./Warehouse";
import { ICorporation } from "./ICorporation";
import { IIndustry } from "./IIndustry";
import { Corporation } from "./Corporation";
interface IParams {
name?: string;
corp?: ICorporation;
corp?: Corporation;
type?: string;
}
export class Industry implements IIndustry {
export class Industry {
name = "";
type = Industries.Agriculture;
sciResearch = new Material({ name: "Scientific Research" });
@ -356,9 +355,7 @@ export class Industry implements IIndustry {
for (let i = 0; i < CorporationConstants.Cities.length; ++i) {
const city = CorporationConstants.Cities[i];
const warehouse = this.warehouses[city];
if (!(warehouse instanceof Warehouse)) {
continue;
}
if (!warehouse) continue;
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;
//At the start of a cycle, store and reset revenue/expenses
@ -414,10 +411,7 @@ export class Industry implements IIndustry {
let employeeSalary = 0;
for (const officeLoc of Object.keys(this.offices)) {
const office = this.offices[officeLoc];
if (office === 0) continue;
if (office instanceof OfficeSpace) {
employeeSalary += office.process(marketCycles, corporation, this);
}
if (office) employeeSalary += office.process(marketCycles, corporation, this);
}
this.thisCycleExpenses = this.thisCycleExpenses + employeeSalary;
@ -468,7 +462,7 @@ export class Industry implements IIndustry {
for (let i = 0; i < CorporationConstants.Cities.length; ++i) {
//If this industry has a warehouse in this city, process the market
//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]];
if (wh === 0) continue;
for (const name of Object.keys(reqMats)) {
@ -518,7 +512,7 @@ export class Industry implements IIndustry {
}
//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,
expenses = 0;
this.calculateProductionFactors();
@ -528,7 +522,7 @@ export class Industry implements IIndustry {
const office = this.offices[city];
if (office === 0) continue;
if (this.warehouses[city] instanceof Warehouse) {
if (this.warehouses[city]) {
const warehouse = this.warehouses[city];
if (warehouse === 0) continue;
@ -825,14 +819,7 @@ export class Industry implements IIndustry {
sellAmt = eval(tmp);
} catch (e) {
dialogBoxCreate(
"Error evaluating your sell amount for material " +
mat.name +
" in " +
this.name +
"'s " +
city +
" office. The sell amount " +
"is being set to zero",
`Error evaluating your sell amount for material ${mat.name} in ${this.name}'s ${city} office. The sell amount is being set to zero`,
);
sellAmt = 0;
}
@ -879,27 +866,13 @@ export class Industry implements IIndustry {
amt = eval(amtStr);
} catch (e) {
dialogBoxCreate(
"Calculating export for " +
mat.name +
" in " +
this.name +
"'s " +
city +
" division failed with " +
"error: " +
e,
`Calculating export for ${mat.name} in ${this.name}'s ${city} division failed with error: ${e}`,
);
continue;
}
if (isNaN(amt)) {
dialogBoxCreate(
"Error calculating export amount for " +
mat.name +
" in " +
this.name +
"'s " +
city +
" division.",
`Error calculating export amount for ${mat.name} in ${this.name}'s ${city} division.`,
);
continue;
}
@ -915,7 +888,7 @@ export class Industry implements IIndustry {
if (corporation.divisions[foo].name === exp.ind) {
const expIndustry = corporation.divisions[foo];
const expWarehouse = expIndustry.warehouses[exp.city];
if (!(expWarehouse instanceof Warehouse)) {
if (!expWarehouse) {
console.error(`Invalid export! ${expIndustry.name} ${exp.city}`);
break;
}
@ -958,7 +931,7 @@ export class Industry implements IIndustry {
//Produce Scientific Research based on R&D employees
//Scientific Research can be produced without a warehouse
if (office instanceof OfficeSpace) {
if (office) {
this.sciResearch.qty +=
0.004 *
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)
processProducts(marketCycles = 1, corporation: ICorporation): [number, number] {
processProducts(marketCycles = 1, corporation: Corporation): [number, number] {
let revenue = 0;
const expenses = 0;
@ -997,7 +970,7 @@ export class Industry implements IIndustry {
for (const prodName of Object.keys(this.products)) {
if (this.products.hasOwnProperty(prodName)) {
const prod = this.products[prodName];
if (prod instanceof Product && prod.fin) {
if (prod && prod.fin) {
revenue += this.processProduct(marketCycles, prod, corporation);
}
}
@ -1006,14 +979,14 @@ export class Industry implements IIndustry {
}
//Processes FINISHED products
processProduct(marketCycles = 1, product: Product, corporation: ICorporation): number {
processProduct(marketCycles = 1, product: Product, corporation: Corporation): number {
let totalProfit = 0;
for (let i = 0; i < CorporationConstants.Cities.length; ++i) {
const city = CorporationConstants.Cities[i];
const office = this.offices[city];
if (office === 0) continue;
const warehouse = this.warehouses[city];
if (warehouse instanceof Warehouse) {
if (warehouse) {
switch (this.state) {
case "PRODUCTION": {
//Calculate the maximum production of this material based
@ -1172,13 +1145,7 @@ export class Industry implements IIndustry {
tmp = eval(tmp);
} catch (e) {
dialogBoxCreate(
"Error evaluating your sell price expression for " +
product.name +
" in " +
this.name +
"'s " +
city +
" office. Sell price is being set to MAX",
`Error evaluating your sell price expression for ${product.name} in ${this.name}'s ${city} office. Sell price is being set to MAX`,
);
tmp = product.maxsll;
}
@ -1223,7 +1190,7 @@ export class Industry implements IIndustry {
if (state === "EXPORT") {
for (let i = 0; i < CorporationConstants.Cities.length; ++i) {
const city = CorporationConstants.Cities[i];
if (!(this.warehouses[city] instanceof Warehouse)) {
if (!this.warehouses[city]) {
continue;
}
const warehouse = this.warehouses[city];
@ -1252,7 +1219,7 @@ export class Industry implements IIndustry {
return 1e9 * Math.pow(1.06, this.numAdVerts);
}
applyAdVert(corporation: ICorporation): void {
applyAdVert(corporation: Corporation): void {
const advMult = corporation.getAdvertisingMultiplier() * this.getAdvertisingMultiplier();
const awareness = (this.awareness + 3 * advMult) * (1.01 * advMult);
this.awareness = Math.min(awareness, Number.MAX_VALUE);

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

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

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

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

@ -1,39 +1,7 @@
import { CityName } from "./../../Locations/data/CityNames";
const CyclesPerMarketCycle = 50;
const AllCorporationStates = ["START", "PURCHASE", "PRODUCTION", "SALE", "EXPORT"];
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;
} = {
export const CorporationConstants = {
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
IssueNewSharesCooldown: 216e3, // 12 Hour in terms of game cycles
@ -96,7 +64,7 @@ export const CorporationConstants: {
"Tobacco",
"Chemical",
"Pharmaceutical",
"Hardware",
"Computers",
"Robotics",
"Software",
"Healthcare",

@ -1,14 +1,11 @@
import React, { useContext } from "react";
import { ICorporation } from "../ICorporation";
import { IIndustry } from "../IIndustry";
import { Corporation } from "../Corporation";
import { Industry } from "../Industry";
export const Context: {
Corporation: React.Context<ICorporation>;
Division: React.Context<IIndustry>;
} = {
Corporation: React.createContext<ICorporation>({} as ICorporation),
Division: React.createContext<IIndustry>({} as IIndustry),
export const Context = {
Corporation: React.createContext<Corporation>({} as Corporation),
Division: React.createContext<Industry>({} as Industry),
};
export const useCorporation = (): ICorporation => useContext(Context.Corporation);
export const useDivision = (): IIndustry => useContext(Context.Division);
export const useCorporation = (): Corporation => useContext(Context.Corporation);
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
// divisions, see an overview of your corporation, or create a new industry
import React, { useState, useEffect } from "react";
import { IIndustry } from "../IIndustry";
import { Industry } from "../Industry";
import { MainPanel } from "./MainPanel";
import { Industries } from "../IndustryData";
import { ExpandIndustryTab } from "./ExpandIndustryTab";
import { use } from "../../ui/Context";
import { Player } from "../../Player";
import { Context } from "./Context";
import { Overview } from "./Overview";
@ -14,8 +14,7 @@ import Tabs from "@mui/material/Tabs";
import Tab from "@mui/material/Tab";
export function CorporationRoot(): React.ReactElement {
const player = use.Player();
const corporation = player.corporation;
const corporation = Player.corporation;
if (corporation === null) return <></>;
const setRerender = useState(false)[1];
function rerender(): void {
@ -33,7 +32,7 @@ export function CorporationRoot(): React.ReactElement {
const canExpand =
Object.keys(Industries).filter(
(industryType: string) =>
corporation.divisions.find((division: IIndustry) => division.type === industryType) === undefined,
corporation.divisions.find((division: Industry) => division.type === industryType) === undefined,
).length > 0;
return (

@ -2,7 +2,7 @@ import React, { useState } from "react";
import { dialogBoxCreate } from "../../ui/React/DialogBox";
import { IndustryStartingCosts, Industries, IndustryDescriptions } from "../IndustryData";
import { useCorporation } from "./Context";
import { IIndustry } from "../IIndustry";
import { Industry } from "../Industry";
import { NewIndustry } from "../Actions";
import Typography from "@mui/material/Typography";
@ -23,7 +23,7 @@ export function ExpandIndustryTab(props: IProps): React.ReactElement {
const possibleIndustries = allIndustries
.filter(
(industryType: string) =>
corp.divisions.find((division: IIndustry) => division.type === industryType) === undefined,
corp.divisions.find((division: Industry) => division.type === industryType) === undefined,
)
.sort();
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
// current industry.
export function isRelevantMaterial(matName: string, division: IIndustry): boolean {
export function isRelevantMaterial(matName: string, division: Industry): boolean {
// Materials that affect Production multiplier
const prodMultiplierMats = ["Hardware", "Robots", "AICores", "RealEstate", "AI Cores", "Real Estate"];

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

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

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

@ -4,7 +4,7 @@
import React from "react";
import { CityTabs } from "./CityTabs";
import { IIndustry } from "../IIndustry";
import { Industry } from "../Industry";
import { Context, useCorporation } from "./Context";
import { CityName } from "../../Locations/data/CityNames";
@ -18,7 +18,7 @@ export function MainPanel(props: IProps): React.ReactElement {
const corp = useCorporation();
const division =
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
if (division === undefined) throw new Error("Cannot find division");

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

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

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

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

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

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

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

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

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

@ -2,9 +2,8 @@ import React, { useState } from "react";
import { numeralWrapper } from "../../../ui/numeralFormat";
import { dialogBoxCreate } from "../../../ui/React/DialogBox";
import { Modal } from "../../../ui/React/Modal";
import { use } from "../../../ui/Context";
import { useCorporation } from "../Context";
import { ICorporation } from "../../ICorporation";
import { Corporation } from "../../Corporation";
import Typography from "@mui/material/Typography";
import Button from "@mui/material/Button";
import { Money } from "../../../ui/React/Money";
@ -20,13 +19,12 @@ interface IProps {
// 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
export function SellSharesModal(props: IProps): React.ReactElement {
const player = use.Player();
const corp = useCorporation();
const [shares, setShares] = useState<number>(NaN);
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 <></>;
let text = "";
if (isNaN(props.shares) || props.shares <= 0) {
@ -49,7 +47,7 @@ export function SellSharesModal(props: IProps): React.ReactElement {
function sell(): void {
if (disabled) return;
try {
const profit = SellShares(corp, player, shares);
const profit = SellShares(corp, shares);
props.onClose();
dialogBoxCreate(
<>

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

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

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