Merge pull request #1389 from danielyxie/dev

More Mui work
This commit is contained in:
hydroflame 2021-09-28 20:49:48 -04:00 committed by GitHub
commit af02fe992a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
273 changed files with 4136 additions and 3883 deletions

@ -19,12 +19,13 @@
.popup-box-content { .popup-box-content {
background-color: var(--my-background-color); background-color: var(--my-background-color);
padding: 12px; padding: 12px;
border: 5px solid var(--my-highlight-color); border: 2px solid $hacker-green;
width: 70%; width: 70%;
max-height: 80%; max-height: 80%;
overflow-y: auto; overflow-y: auto;
z-index: 11; /* Sit on top of the container */ z-index: 11; /* Sit on top of the container */
color: var(--my-font-color); color: var(--my-font-color);
box-shadow: 0 3px 5px -1px #090, 0 5px 8px 0 #090, 0 1px 14px 0 #090;
} }
.popup-box-input-div { .popup-box-input-div {
@ -89,13 +90,13 @@
max-height: 50%; max-height: 50%;
z-index: 10; z-index: 10;
background-color: var(--my-background-color); background-color: var(--my-background-color);
border: 2px solid var(--my-highlight-color); border: 2px solid $hacker-green;
} }
.log-box-header { .log-box-header {
z-index: 1300; z-index: 1300;
background-color: #333; background-color: #333;
border: 1px solid var(--my-highlight-color); border: 2px solid $hacker-green;
display: flex; display: flex;
flex: row nowrap; flex: row nowrap;
align-items: center; align-items: center;

56
dist/vendor.bundle.js vendored

File diff suppressed because one or more lines are too long

@ -38,17 +38,7 @@
<link rel="shortcut icon" href="favicon.ico"><link href="dist/vendor.css" rel="stylesheet"><link href="main.css" rel="stylesheet"></head> <link rel="shortcut icon" href="favicon.ico"><link href="dist/vendor.css" rel="stylesheet"><link href="main.css" rel="stylesheet"></head>
<body> <body>
<div id="entire-game-container"> <div id="root"/>
<div id="mainmenu-container" style="display: flex; flex-direction: row"></div>
<!-- Status text -->
<div id="status-text-container">
<p id="status-text"></p>
</div>
</div>
<div id="unclickable" style="display: none">Click on this to upgrade your Source-File -1!</div>
<script type="text/javascript" src="dist/vendor.bundle.js"></script><script type="text/javascript" src="main.bundle.js"></script></body> <script type="text/javascript" src="dist/vendor.bundle.js"></script><script type="text/javascript" src="main.bundle.js"></script></body>
<script src="src/ThirdParty/raphael.min.js"></script> <script src="src/ThirdParty/raphael.min.js"></script>
</html> </html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -2105,13 +2105,14 @@ input[type="checkbox"] {
.popup-box-content { .popup-box-content {
background-color: var(--my-background-color); background-color: var(--my-background-color);
padding: 12px; padding: 12px;
border: 5px solid var(--my-highlight-color); border: 2px solid #adff2f;
width: 70%; width: 70%;
max-height: 80%; max-height: 80%;
overflow-y: auto; overflow-y: auto;
z-index: 11; z-index: 11;
/* Sit on top of the container */ /* Sit on top of the container */
color: var(--my-font-color); } color: var(--my-font-color);
box-shadow: 0 3px 5px -1px #090, 0 5px 8px 0 #090, 0 1px 14px 0 #090; }
.popup-box-input-div { .popup-box-input-div {
margin: 2px; } margin: 2px; }
@ -2169,12 +2170,12 @@ input[type="checkbox"] {
max-height: 50%; max-height: 50%;
z-index: 10; z-index: 10;
background-color: var(--my-background-color); background-color: var(--my-background-color);
border: 2px solid var(--my-highlight-color); } border: 2px solid #adff2f; }
.log-box-header { .log-box-header {
z-index: 1300; z-index: 1300;
background-color: #333; background-color: #333;
border: 1px solid var(--my-highlight-color); border: 2px solid #adff2f;
display: flex; display: flex;
flex: row nowrap; flex: row nowrap;
align-items: center; align-items: center;

File diff suppressed because one or more lines are too long

@ -8,7 +8,7 @@ import { Factions } from "../Faction/Factions";
import { numeralWrapper } from "../ui/numeralFormat"; import { numeralWrapper } from "../ui/numeralFormat";
import { Money } from "../ui/React/Money"; import { Money } from "../ui/React/Money";
import { Generic_fromJSON, Generic_toJSON, Reviver } from "../../utils/JSONReviver"; import { Generic_fromJSON, Generic_toJSON, Reviver } from "../utils/JSONReviver";
export interface IConstructorParams { export interface IConstructorParams {
info: string | JSX.Element; info: string | JSX.Element;

@ -11,8 +11,8 @@ import { prestigeAugmentation } from "../Prestige";
import { Programs } from "../Programs/Programs"; import { Programs } from "../Programs/Programs";
import { SourceFileFlags } from "../SourceFile/SourceFileFlags"; import { SourceFileFlags } from "../SourceFile/SourceFileFlags";
import { dialogBoxCreate } from "../../utils/DialogBox"; import { dialogBoxCreate } from "../ui/React/DialogBox";
import { clearObject } from "../../utils/helpers/clearObject"; import { clearObject } from "../utils/helpers/clearObject";
import { WHRNG } from "../Casino/RNG"; import { WHRNG } from "../Casino/RNG";
@ -2044,6 +2044,28 @@ function initAugmentations(): void {
} }
AddToAugmentations(SNA); AddToAugmentations(SNA);
const NeuroreceptorManager = new Augmentation({
name: AugmentationNames.NeuroreceptorManager,
repCost: 0.75e5,
moneyCost: 5.5e8,
info:
"A brain implant carefully assembled around the synapses, which " +
"micromanages the activity and levels of various neuroreceptor " +
"chemicals and modulates electrical acvitiy to optimize concentration, " +
"allowing the user to multitask much more effectively.",
stats: (
<>
This augmentation removes the penalty for not focusing on actions such as working in a job or working for a
faction.
</>
),
});
NeuroreceptorManager.addToFactions(["Tian Di Hui"]);
if (augmentationExists(AugmentationNames.NeuroreceptorManager)) {
delete Augmentations[AugmentationNames.NeuroreceptorManager];
}
AddToAugmentations(NeuroreceptorManager);
// Special Bladeburner Augmentations // Special Bladeburner Augmentations
const BladeburnersFactionName = "Bladeburners"; const BladeburnersFactionName = "Bladeburners";
if (factionExists(BladeburnersFactionName)) { if (factionExists(BladeburnersFactionName)) {

@ -41,6 +41,7 @@ export const AugmentationNames: IMap<string> = {
CranialSignalProcessorsG4: "Cranial Signal Processors - Gen IV", CranialSignalProcessorsG4: "Cranial Signal Processors - Gen IV",
CranialSignalProcessorsG5: "Cranial Signal Processors - Gen V", CranialSignalProcessorsG5: "Cranial Signal Processors - Gen V",
NeuronalDensification: "Neuronal Densification", NeuronalDensification: "Neuronal Densification",
NeuroreceptorManager: "Neuroreceptor Management Implant",
NuoptimalInjectorImplant: "Nuoptimal Nootropic Injector Implant", NuoptimalInjectorImplant: "Nuoptimal Nootropic Injector Implant",
SpeechEnhancement: "Speech Enhancement", SpeechEnhancement: "Speech Enhancement",
FocusWire: "FocusWire", FocusWire: "FocusWire",

@ -2,7 +2,7 @@
* Root React component for the Augmentations UI page that display all of your * Root React component for the Augmentations UI page that display all of your
* owned and purchased Augmentations and Source-Files. * owned and purchased Augmentations and Source-Files.
*/ */
import React, { useState } from "react"; import React, { useState, useEffect } from "react";
import { InstalledAugmentations } from "./InstalledAugmentations"; import { InstalledAugmentations } from "./InstalledAugmentations";
import { PlayerMultipliers } from "./PlayerMultipliers"; import { PlayerMultipliers } from "./PlayerMultipliers";
@ -23,10 +23,17 @@ interface IProps {
export function AugmentationsRoot(props: IProps): React.ReactElement { export function AugmentationsRoot(props: IProps): React.ReactElement {
const setRerender = useState(false)[1]; const setRerender = useState(false)[1];
function rerender(): void {
setRerender((o) => !o);
}
useEffect(() => {
const id = setInterval(rerender, 200);
return () => clearInterval(id);
}, []);
function doExport(): void { function doExport(): void {
props.exportGameFn(); props.exportGameFn();
setRerender((o) => !o); rerender();
} }
function exportBonusStr(): string { function exportBonusStr(): string {
@ -63,13 +70,11 @@ export function AugmentationsRoot(props: IProps): React.ReactElement {
</Typography> </Typography>
<Box mx={2}> <Box mx={2}>
<Tooltip title={"'I never asked for this'"}> <Tooltip title={"'I never asked for this'"}>
<Button onClick={props.installAugmentationsFn}> <Button onClick={props.installAugmentationsFn}>Install Augmentations</Button>
<Typography>Install Augmentations</Typography>
</Button>
</Tooltip> </Tooltip>
<Tooltip title={"It's always a good idea to backup/export your save!"}> <Tooltip title={"It's always a good idea to backup/export your save!"}>
<Button sx={{ mx: 2 }} onClick={doExport}> <Button sx={{ mx: 2 }} onClick={doExport} color="error">
<Typography color="error">Backup Save {exportBonusStr()}</Typography> Backup Save {exportBonusStr()}
</Button> </Button>
</Tooltip> </Tooltip>
<PurchasedAugmentations /> <PurchasedAugmentations />

@ -1,7 +1,8 @@
import React from "react";
import { Player } from "../Player"; import { Player } from "../Player";
import { getRandomInt } from "../../utils/helpers/getRandomInt"; import { getRandomInt } from "../utils/helpers/getRandomInt";
import { addOffset } from "../../utils/helpers/addOffset"; import { addOffset } from "../utils/helpers/addOffset";
import { Generic_fromJSON, Generic_toJSON, Reviver } from "../../utils/JSONReviver"; import { Generic_fromJSON, Generic_toJSON, Reviver } from "../utils/JSONReviver";
import { BladeburnerConstants } from "./data/Constants"; import { BladeburnerConstants } from "./data/Constants";
import { IBladeburner } from "./IBladeburner"; import { IBladeburner } from "./IBladeburner";
import { IAction, ISuccessChanceParams } from "./IAction"; import { IAction, ISuccessChanceParams } from "./IAction";
@ -20,7 +21,6 @@ class StatsMultiplier {
export interface IActionParams { export interface IActionParams {
name?: string; name?: string;
desc?: string;
level?: number; level?: number;
maxLevel?: number; maxLevel?: number;
autoLevel?: boolean; autoLevel?: boolean;
@ -43,7 +43,6 @@ export interface IActionParams {
export class Action implements IAction { export class Action implements IAction {
name = ""; name = "";
desc = "";
// Difficulty scales with level. See getDifficulty() method // Difficulty scales with level. See getDifficulty() method
level = 1; level = 1;
@ -100,7 +99,6 @@ export class Action implements IAction {
constructor(params: IActionParams | null = null) { constructor(params: IActionParams | null = null) {
// | null = null // | null = null
if (params && params.name) this.name = params.name; if (params && params.name) this.name = params.name;
if (params && params.desc) this.desc = params.desc;
if (params && params.baseDifficulty) this.baseDifficulty = addOffset(params.baseDifficulty, 10); if (params && params.baseDifficulty) this.baseDifficulty = addOffset(params.baseDifficulty, 10);
if (params && params.difficultyFac) this.difficultyFac = params.difficultyFac; if (params && params.difficultyFac) this.difficultyFac = params.difficultyFac;

@ -1,5 +1,5 @@
import { IActionIdentifier } from "./IActionIdentifier"; import { IActionIdentifier } from "./IActionIdentifier";
import { Generic_fromJSON, Generic_toJSON, Reviver } from "../../utils/JSONReviver"; import { Generic_fromJSON, Generic_toJSON, Reviver } from "../utils/JSONReviver";
interface IParams { interface IParams {
name?: string; name?: string;

@ -1,5 +1,5 @@
import { Operation, IOperationParams } from "./Operation"; import { Operation, IOperationParams } from "./Operation";
import { Generic_fromJSON, Generic_toJSON, Reviver } from "../../utils/JSONReviver"; import { Generic_fromJSON, Generic_toJSON, Reviver } from "../utils/JSONReviver";
export class BlackOperation extends Operation { export class BlackOperation extends Operation {
constructor(params: IOperationParams | null = null) { constructor(params: IOperationParams | null = null) {

@ -1,763 +0,0 @@
import { BlackOperation } from "./BlackOperation";
import { IMap } from "../types";
export const BlackOperations: IMap<BlackOperation> = {};
(function () {
BlackOperations["Operation Typhoon"] = new BlackOperation({
name: "Operation Typhoon",
desc:
"Obadiah Zenyatta is the leader of a RedWater PMC. It has long " +
"been known among the intelligence community that Zenyatta, along " +
"with the rest of the PMC, is a Synthoid.<br><br>" +
"The goal of Operation Typhoon is to find and eliminate " +
"Zenyatta and RedWater by any means necessary. After the task " +
"is completed, the actions must be covered up from the general public.",
baseDifficulty: 2000,
reqdRank: 2.5e3,
rankGain: 50,
rankLoss: 10,
hpLoss: 100,
weights: {
hack: 0.1,
str: 0.2,
def: 0.2,
dex: 0.2,
agi: 0.2,
cha: 0,
int: 0.1,
},
decays: {
hack: 0.6,
str: 0.8,
def: 0.8,
dex: 0.8,
agi: 0.8,
cha: 0,
int: 0.75,
},
isKill: true,
});
BlackOperations["Operation Zero"] = new BlackOperation({
name: "Operation Zero",
desc:
"AeroCorp is one of the world's largest defense contractors. " +
"Its leader, Steve Watataki, is thought to be a supporter of " +
"Synthoid rights. He must be removed.<br><br>" +
"The goal of Operation Zero is to covertly infiltrate AeroCorp and " +
"uncover any incriminating evidence or " +
"information against Watataki that will cause him to be removed " +
"from his position at AeroCorp. Incriminating evidence can be " +
"fabricated as a last resort. Be warned that AeroCorp has some of " +
"the most advanced security measures in the world.",
baseDifficulty: 2500,
reqdRank: 5e3,
rankGain: 60,
rankLoss: 15,
hpLoss: 50,
weights: {
hack: 0.2,
str: 0.15,
def: 0.15,
dex: 0.2,
agi: 0.2,
cha: 0,
int: 0.1,
},
decays: {
hack: 0.6,
str: 0.8,
def: 0.8,
dex: 0.8,
agi: 0.8,
cha: 0,
int: 0.75,
},
isStealth: true,
});
BlackOperations["Operation X"] = new BlackOperation({
name: "Operation X",
desc:
"We have recently discovered an underground publication " +
"group called Samizdat. Even though most of their publications " +
"are nonsensical conspiracy theories, the average human is " +
"gullible enough to believe them. Many of their works discuss " +
"Synthoids and pose a threat to society. The publications are spreading " +
"rapidly in China and other Eastern countries.<br><br>" +
"Samizdat has done a good job of keeping hidden and anonymous. " +
"However, we've just received intelligence that their base of " +
"operations is in Ishima's underground sewer systems. Your task is to " +
"investigate the sewer systems, and eliminate Samizdat. They must " +
"never publish anything again.",
baseDifficulty: 3000,
reqdRank: 7.5e3,
rankGain: 75,
rankLoss: 15,
hpLoss: 100,
weights: {
hack: 0.1,
str: 0.2,
def: 0.2,
dex: 0.2,
agi: 0.2,
cha: 0,
int: 0.1,
},
decays: {
hack: 0.6,
str: 0.8,
def: 0.8,
dex: 0.8,
agi: 0.8,
cha: 0,
int: 0.75,
},
isKill: true,
});
BlackOperations["Operation Titan"] = new BlackOperation({
name: "Operation Titan",
desc:
"Several months ago Titan Laboratories' Bioengineering department " +
"was infiltrated by Synthoids. As far as we know, Titan Laboratories' " +
"management has no knowledge about this. We don't know what the " +
"Synthoids are up to, but the research that they could " +
"be conducting using Titan Laboraties' vast resources is potentially " +
"very dangerous.<br><br>" +
"Your goal is to enter and destroy the Bioengineering department's " +
"facility in Aevum. The task is not just to retire the Synthoids there, but " +
"also to destroy any information or research at the facility that " +
"is relevant to the Synthoids and their goals.",
baseDifficulty: 4000,
reqdRank: 10e3,
rankGain: 100,
rankLoss: 20,
hpLoss: 100,
weights: {
hack: 0.1,
str: 0.2,
def: 0.2,
dex: 0.2,
agi: 0.2,
cha: 0,
int: 0.1,
},
decays: {
hack: 0.6,
str: 0.8,
def: 0.8,
dex: 0.8,
agi: 0.8,
cha: 0,
int: 0.75,
},
isKill: true,
});
BlackOperations["Operation Ares"] = new BlackOperation({
name: "Operation Ares",
desc:
"One of our undercover agents, Agent Carter, has informed us of a " +
"massive weapons deal going down in Dubai between rogue Russian " +
"militants and a radical Synthoid community. These weapons are next-gen " +
"plasma and energy weapons. It is critical for the safety of humanity " +
"that this deal does not happen.<br><br>" +
"Your task is to intercept the deal. Leave no survivors.",
baseDifficulty: 5000,
reqdRank: 12.5e3,
rankGain: 125,
rankLoss: 20,
hpLoss: 200,
weights: {
hack: 0,
str: 0.25,
def: 0.25,
dex: 0.25,
agi: 0.25,
cha: 0,
int: 0,
},
decays: {
hack: 0,
str: 0.8,
def: 0.8,
dex: 0.8,
agi: 0.8,
cha: 0,
int: 0.75,
},
isKill: true,
});
BlackOperations["Operation Archangel"] = new BlackOperation({
name: "Operation Archangel",
desc:
"Our analysts have discovered that the popular Red Rabbit brothel in " +
"Amsterdam is run and 'staffed' by MK-VI Synthoids. Intelligence " +
"suggests that the profit from this brothel is used to fund a large " +
"black market arms trafficking operation.<br><br>" +
"The goal of this operation is to take out the leaders that are running " +
"the Red Rabbit brothel. Try to limit the number of other casualties, " +
"but do what you must to complete the mission.",
baseDifficulty: 7500,
reqdRank: 15e3,
rankGain: 200,
rankLoss: 20,
hpLoss: 25,
weights: {
hack: 0,
str: 0.2,
def: 0.2,
dex: 0.3,
agi: 0.3,
cha: 0,
int: 0,
},
decays: {
hack: 0,
str: 0.8,
def: 0.8,
dex: 0.8,
agi: 0.8,
cha: 0,
int: 0.75,
},
isKill: true,
});
BlackOperations["Operation Juggernaut"] = new BlackOperation({
name: "Operation Juggernaut",
desc:
"The CIA has just encountered a new security threat. A new " +
"criminal group, lead by a shadowy operative who calls himself " +
"Juggernaut, has been smuggling drugs and weapons (including " +
"suspected bioweapons) into Sector-12. We also have reason " +
"to believe the tried to break into one of Universal Energy's " +
"facilities in order to cause a city-wide blackout. The CIA " +
"suspects that Juggernaut is a heavily-augmented Synthoid, and " +
"have thus enlisted our help.<br><br>" +
"Your mission is to eradicate Juggernaut and his followers.",
baseDifficulty: 10e3,
reqdRank: 20e3,
rankGain: 300,
rankLoss: 40,
hpLoss: 300,
weights: {
hack: 0,
str: 0.25,
def: 0.25,
dex: 0.25,
agi: 0.25,
cha: 0,
int: 0,
},
decays: {
hack: 0,
str: 0.8,
def: 0.8,
dex: 0.8,
agi: 0.8,
cha: 0,
int: 0.75,
},
isKill: true,
});
BlackOperations["Operation Red Dragon"] = new BlackOperation({
name: "Operation Red Dragon",
desc:
"The Tetrads criminal organization is suspected of " +
"reverse-engineering the MK-VI Synthoid design. We believe " +
"they altered and possibly improved the design and began " +
"manufacturing their own Synthoid models in order to bolster " +
"their criminal activities.<br><br>" +
"Your task is to infiltrate and destroy the Tetrads' base of operations " +
"in Los Angeles. Intelligence tells us that their base houses " +
"one of their Synthoid manufacturing units.",
baseDifficulty: 12.5e3,
reqdRank: 25e3,
rankGain: 500,
rankLoss: 50,
hpLoss: 500,
weights: {
hack: 0.05,
str: 0.2,
def: 0.2,
dex: 0.25,
agi: 0.25,
cha: 0,
int: 0.05,
},
decays: {
hack: 0.6,
str: 0.8,
def: 0.8,
dex: 0.8,
agi: 0.8,
cha: 0,
int: 0.75,
},
isKill: true,
});
BlackOperations["Operation K"] = new BlackOperation({
name: "Operation K",
desc:
"CODE RED SITUATION. Our intelligence tells us that VitaLife " +
"has discovered a new android cloning technology. This technology " +
"is supposedly capable of cloning Synthoid, not only physically " +
"but also their advanced AI modules. We do not believe that " +
"VitaLife is trying to use this technology illegally or " +
"maliciously, but if any Synthoids were able to infiltrate the " +
"corporation and take advantage of this technology then the " +
"results would be catastrophic.<br><br>" +
"We do not have the power or jurisdiction to shutdown this down " +
"through legal or political means, so we must resort to a covert " +
"operation. Your goal is to destroy this technology and eliminate " +
"anyone who was involved in its creation.",
baseDifficulty: 15e3,
reqdRank: 30e3,
rankGain: 750,
rankLoss: 60,
hpLoss: 1000,
weights: {
hack: 0.05,
str: 0.2,
def: 0.2,
dex: 0.25,
agi: 0.25,
cha: 0,
int: 0.05,
},
decays: {
hack: 0.6,
str: 0.8,
def: 0.8,
dex: 0.8,
agi: 0.8,
cha: 0,
int: 0.75,
},
isKill: true,
});
BlackOperations["Operation Deckard"] = new BlackOperation({
name: "Operation Deckard",
desc:
"Despite your success in eliminating VitaLife's new android-replicating " +
"technology in Operation K, we've discovered that a small group of " +
"MK-VI Synthoids were able to make off with the schematics and design " +
"of the technology before the Operation. It is almost a certainty that " +
"these Synthoids are some of the rogue MK-VI ones from the Synthoid Uprising.<br><br>" +
"The goal of Operation Deckard is to hunt down these Synthoids and retire " +
"them. I don't need to tell you how critical this mission is.",
baseDifficulty: 20e3,
reqdRank: 40e3,
rankGain: 1e3,
rankLoss: 75,
hpLoss: 200,
weights: {
hack: 0,
str: 0.24,
def: 0.24,
dex: 0.24,
agi: 0.24,
cha: 0,
int: 0.04,
},
decays: {
hack: 0,
str: 0.8,
def: 0.8,
dex: 0.8,
agi: 0.8,
cha: 0,
int: 0.75,
},
isKill: true,
});
BlackOperations["Operation Tyrell"] = new BlackOperation({
name: "Operation Tyrell",
desc:
"A week ago Blade Industries reported a small break-in at one " +
"of their Aevum Augmentation storage facitilities. We figured out " +
"that The Dark Army was behind the heist, and didn't think any more " +
"of it. However, we've just discovered that several known MK-VI Synthoids " +
"were part of that break-in group.<br><br>" +
"We cannot have Synthoids upgrading their already-enhanced abilities " +
"with Augmentations. Your task is to hunt down the associated Dark Army " +
"members and eliminate them.",
baseDifficulty: 25e3,
reqdRank: 50e3,
rankGain: 1.5e3,
rankLoss: 100,
hpLoss: 500,
weights: {
hack: 0.1,
str: 0.2,
def: 0.2,
dex: 0.2,
agi: 0.2,
cha: 0,
int: 0.1,
},
decays: {
hack: 0.6,
str: 0.8,
def: 0.8,
dex: 0.8,
agi: 0.8,
cha: 0,
int: 0.75,
},
isKill: true,
});
BlackOperations["Operation Wallace"] = new BlackOperation({
name: "Operation Wallace",
desc:
"Based on information gathered from Operation Tyrell, we've discovered " +
"that The Dark Army was well aware that there were Synthoids amongst " +
"their ranks. Even worse, we believe that The Dark Army is working " +
"together with other criminal organizations such as The Syndicate and " +
"that they are planning some sort of large-scale takeover of multiple major " +
"cities, most notably Aevum. We suspect that Synthoids have infiltrated " +
"the ranks of these criminal factions and are trying to stage another " +
"Synthoid uprising.<br><br>" +
"The best way to deal with this is to prevent it before it even happens. " +
"The goal of Operation Wallace is to destroy the Dark Army and " +
"Syndicate factions in Aevum immediately. Leave no survivors.",
baseDifficulty: 30e3,
reqdRank: 75e3,
rankGain: 2e3,
rankLoss: 150,
hpLoss: 1500,
weights: {
hack: 0,
str: 0.24,
def: 0.24,
dex: 0.24,
agi: 0.24,
cha: 0,
int: 0.04,
},
decays: {
hack: 0.6,
str: 0.8,
def: 0.8,
dex: 0.8,
agi: 0.8,
cha: 0,
int: 0.75,
},
isKill: true,
});
BlackOperations["Operation Shoulder of Orion"] = new BlackOperation({
name: "Operation Shoulder of Orion",
desc:
"China's Solaris Space Systems is secretly launching the first " +
"manned spacecraft in over a decade using Synthoids. We believe " +
"China is trying to establish the first off-world colonies.<br><br>" +
"The mission is to prevent this launch without instigating an " +
"international conflict. When you accept this mission you will be " +
"officially disavowed by the NSA and the national government until after you " +
"successfully return. In the event of failure, all of the operation's " +
"team members must not let themselves be captured alive.",
baseDifficulty: 35e3,
reqdRank: 100e3,
rankGain: 2.5e3,
rankLoss: 500,
hpLoss: 1500,
weights: {
hack: 0.1,
str: 0.2,
def: 0.2,
dex: 0.2,
agi: 0.2,
cha: 0,
int: 0.1,
},
decays: {
hack: 0.6,
str: 0.8,
def: 0.8,
dex: 0.8,
agi: 0.8,
cha: 0,
int: 0.75,
},
isStealth: true,
});
BlackOperations["Operation Hyron"] = new BlackOperation({
name: "Operation Hyron",
desc:
"Our intelligence tells us that Fulcrum Technologies is developing " +
"a quantum supercomputer using human brains as core " +
"processors. This supercomputer " +
"is rumored to be able to store vast amounts of data and " +
"perform computations unmatched by any other supercomputer on the " +
"planet. But more importantly, the use of organic human brains " +
"means that the supercomputer may be able to reason abstractly " +
"and become self-aware.<br><br>" +
"I do not need to remind you why sentient-level AIs pose a serious " +
"threat to all of mankind.<br><br>" +
"The research for this project is being conducted at one of Fulcrum " +
"Technologies secret facilities in Aevum, codenamed 'Alpha Ranch'. " +
"Infiltrate the compound, delete and destroy the work, and then find and kill the " +
"project lead.",
baseDifficulty: 40e3,
reqdRank: 125e3,
rankGain: 3e3,
rankLoss: 1e3,
hpLoss: 500,
weights: {
hack: 0.1,
str: 0.2,
def: 0.2,
dex: 0.2,
agi: 0.2,
cha: 0,
int: 0.1,
},
decays: {
hack: 0.6,
str: 0.8,
def: 0.8,
dex: 0.8,
agi: 0.8,
cha: 0,
int: 0.75,
},
isKill: true,
});
BlackOperations["Operation Morpheus"] = new BlackOperation({
name: "Operation Morpheus",
desc:
"DreamSense Technologies is an advertising company that uses " +
"special technology to transmit their ads into the peoples " +
"dreams and subconcious. They do this using broadcast transmitter " +
"towers. Based on information from our agents and informants in " +
"Chonqging, we have reason to believe that one of the broadcast " +
"towers there has been compromised by Synthoids and is being used " +
"to spread pro-Synthoid propaganda.<br><br>" +
"The mission is to destroy this broadcast tower. Speed and " +
"stealth are of the upmost important for this.",
baseDifficulty: 45e3,
reqdRank: 150e3,
rankGain: 4e3,
rankLoss: 1e3,
hpLoss: 100,
weights: {
hack: 0.05,
str: 0.15,
def: 0.15,
dex: 0.3,
agi: 0.3,
cha: 0,
int: 0.05,
},
decays: {
hack: 0.6,
str: 0.8,
def: 0.8,
dex: 0.8,
agi: 0.8,
cha: 0,
int: 0.75,
},
isStealth: true,
});
BlackOperations["Operation Ion Storm"] = new BlackOperation({
name: "Operation Ion Storm",
desc:
"Our analysts have uncovered a gathering of MK-VI Synthoids " +
"that have taken up residence in the Sector-12 Slums. We " +
"don't know if they are rogue Synthoids from the Uprising, " +
"but we do know that they have been stockpiling " +
"weapons, money, and other resources. This makes them dangerous.<br><br>" +
"This is a full-scale assault operation to find and retire all of these " +
"Synthoids in the Sector-12 Slums.",
baseDifficulty: 50e3,
reqdRank: 175e3,
rankGain: 5e3,
rankLoss: 1e3,
hpLoss: 5000,
weights: {
hack: 0,
str: 0.24,
def: 0.24,
dex: 0.24,
agi: 0.24,
cha: 0,
int: 0.04,
},
decays: {
hack: 0.6,
str: 0.8,
def: 0.8,
dex: 0.8,
agi: 0.8,
cha: 0,
int: 0.75,
},
isKill: true,
});
BlackOperations["Operation Annihilus"] = new BlackOperation({
name: "Operation Annihilus",
desc:
"Our superiors have ordered us to eradicate everything and everyone " +
"in an underground facility located in Aevum. They tell us " +
"that the facility houses many dangerous Synthoids and " +
"belongs to a terrorist organization called " +
"'The Covenant'. We have no prior intelligence about this " +
"organization, so you are going in blind.",
baseDifficulty: 55e3,
reqdRank: 200e3,
rankGain: 7.5e3,
rankLoss: 1e3,
hpLoss: 10e3,
weights: {
hack: 0,
str: 0.24,
def: 0.24,
dex: 0.24,
agi: 0.24,
cha: 0,
int: 0.04,
},
decays: {
hack: 0.6,
str: 0.8,
def: 0.8,
dex: 0.8,
agi: 0.8,
cha: 0,
int: 0.75,
},
isKill: true,
});
BlackOperations["Operation Ultron"] = new BlackOperation({
name: "Operation Ultron",
desc:
"OmniTek Incorporated, the original designer and manufacturer of Synthoids, " +
"has notified us of a malfunction in their AI design. This malfunction, " +
"when triggered, causes MK-VI Synthoids to become radicalized and seek out " +
"the destruction of humanity. They say that this bug affects all MK-VI Synthoids, " +
"not just the rogue ones from the Uprising.<br><br>" +
"OmniTek has also told us they they believe someone has triggered this " +
"malfunction in a large group of MK-VI Synthoids, and that these newly-radicalized Synthoids " +
"are now amassing in Volhaven to form a terrorist group called Ultron.<br><br>" +
"Intelligence suggests Ultron is heavily armed and that their members are " +
"augmented. We believe Ultron is making moves to take control of " +
"and weaponize DeltaOne's Tactical High-Energy Satellite Laser Array (THESLA).<br><br>" +
"Your task is to find and destroy Ultron.",
baseDifficulty: 60e3,
reqdRank: 250e3,
rankGain: 10e3,
rankLoss: 2e3,
hpLoss: 10e3,
weights: {
hack: 0.1,
str: 0.2,
def: 0.2,
dex: 0.2,
agi: 0.2,
cha: 0,
int: 0.1,
},
decays: {
hack: 0.6,
str: 0.8,
def: 0.8,
dex: 0.8,
agi: 0.8,
cha: 0,
int: 0.75,
},
isKill: true,
});
BlackOperations["Operation Centurion"] = new BlackOperation({
name: "Operation Centurion",
desc:
"D)@#)($M)C0293c40($*)@#D0JUMP3Rm0C<*@#)*$)#02c94830c(#$*D)<br><br>" +
"Throughout all of humanity's history, we have relied on " +
"technology to survive, conquer, and progress. Its advancement became our primary goal. " +
"And at the peak of human civilization technology turned into " +
"power. Global, absolute power.<br><br>" +
"It seems that the universe is not without a sense of irony.<br><br>" +
"D)@#)($M)C0293c40($*)@#D0JUMP3Rm0C<*@#)*$)#02c94830c(#$*D)",
baseDifficulty: 70e3,
reqdRank: 300e3,
rankGain: 15e3,
rankLoss: 5e3,
hpLoss: 10e3,
weights: {
hack: 0.1,
str: 0.2,
def: 0.2,
dex: 0.2,
agi: 0.2,
cha: 0,
int: 0.1,
},
decays: {
hack: 0.6,
str: 0.8,
def: 0.8,
dex: 0.8,
agi: 0.8,
cha: 0,
int: 0.75,
},
});
BlackOperations["Operation Vindictus"] = new BlackOperation({
name: "Operation Vindictus",
desc:
"D)@#)($M)C0293c40($*)@#D0JUMP3Rm0C<*@#)*$)#02c94830c(#$*D)<br><br>" +
"The bits are all around us. The daemons that hold the Node " +
"together can manifest themselves in many different ways.<br><br>" +
"D)@#)($M)C0293c40($*)@#D0JUMP3Rm0C<*@#)*$)#02c94830c(#$*D)",
baseDifficulty: 75e3,
reqdRank: 350e3,
rankGain: 20e3,
rankLoss: 20e3,
hpLoss: 20e3,
weights: {
hack: 0.1,
str: 0.2,
def: 0.2,
dex: 0.2,
agi: 0.2,
cha: 0,
int: 0.1,
},
decays: {
hack: 0.6,
str: 0.8,
def: 0.8,
dex: 0.8,
agi: 0.8,
cha: 0,
int: 0.75,
},
});
BlackOperations["Operation Daedalus"] = new BlackOperation({
name: "Operation Daedalus",
desc: "Yesterday we obeyed kings and bent our neck to emperors. " + "Today we kneel only to truth.",
baseDifficulty: 80e3,
reqdRank: 400e3,
rankGain: 40e3,
rankLoss: 10e3,
hpLoss: 100e3,
weights: {
hack: 0.1,
str: 0.2,
def: 0.2,
dex: 0.2,
agi: 0.2,
cha: 0,
int: 0.1,
},
decays: {
hack: 0.6,
str: 0.8,
def: 0.8,
dex: 0.8,
agi: 0.8,
cha: 0,
int: 0.75,
},
});
})();

@ -0,0 +1,572 @@
import React from "react";
import { BlackOperation } from "./BlackOperation";
import { IMap } from "../types";
export const BlackOperations: IMap<BlackOperation> = {};
(function () {
BlackOperations["Operation Typhoon"] = new BlackOperation({
name: "Operation Typhoon",
baseDifficulty: 2000,
reqdRank: 2.5e3,
rankGain: 50,
rankLoss: 10,
hpLoss: 100,
weights: {
hack: 0.1,
str: 0.2,
def: 0.2,
dex: 0.2,
agi: 0.2,
cha: 0,
int: 0.1,
},
decays: {
hack: 0.6,
str: 0.8,
def: 0.8,
dex: 0.8,
agi: 0.8,
cha: 0,
int: 0.75,
},
isKill: true,
});
BlackOperations["Operation Zero"] = new BlackOperation({
name: "Operation Zero",
baseDifficulty: 2500,
reqdRank: 5e3,
rankGain: 60,
rankLoss: 15,
hpLoss: 50,
weights: {
hack: 0.2,
str: 0.15,
def: 0.15,
dex: 0.2,
agi: 0.2,
cha: 0,
int: 0.1,
},
decays: {
hack: 0.6,
str: 0.8,
def: 0.8,
dex: 0.8,
agi: 0.8,
cha: 0,
int: 0.75,
},
isStealth: true,
});
BlackOperations["Operation X"] = new BlackOperation({
name: "Operation X",
baseDifficulty: 3000,
reqdRank: 7.5e3,
rankGain: 75,
rankLoss: 15,
hpLoss: 100,
weights: {
hack: 0.1,
str: 0.2,
def: 0.2,
dex: 0.2,
agi: 0.2,
cha: 0,
int: 0.1,
},
decays: {
hack: 0.6,
str: 0.8,
def: 0.8,
dex: 0.8,
agi: 0.8,
cha: 0,
int: 0.75,
},
isKill: true,
});
BlackOperations["Operation Titan"] = new BlackOperation({
name: "Operation Titan",
baseDifficulty: 4000,
reqdRank: 10e3,
rankGain: 100,
rankLoss: 20,
hpLoss: 100,
weights: {
hack: 0.1,
str: 0.2,
def: 0.2,
dex: 0.2,
agi: 0.2,
cha: 0,
int: 0.1,
},
decays: {
hack: 0.6,
str: 0.8,
def: 0.8,
dex: 0.8,
agi: 0.8,
cha: 0,
int: 0.75,
},
isKill: true,
});
BlackOperations["Operation Ares"] = new BlackOperation({
name: "Operation Ares",
baseDifficulty: 5000,
reqdRank: 12.5e3,
rankGain: 125,
rankLoss: 20,
hpLoss: 200,
weights: {
hack: 0,
str: 0.25,
def: 0.25,
dex: 0.25,
agi: 0.25,
cha: 0,
int: 0,
},
decays: {
hack: 0,
str: 0.8,
def: 0.8,
dex: 0.8,
agi: 0.8,
cha: 0,
int: 0.75,
},
isKill: true,
});
BlackOperations["Operation Archangel"] = new BlackOperation({
name: "Operation Archangel",
baseDifficulty: 7500,
reqdRank: 15e3,
rankGain: 200,
rankLoss: 20,
hpLoss: 25,
weights: {
hack: 0,
str: 0.2,
def: 0.2,
dex: 0.3,
agi: 0.3,
cha: 0,
int: 0,
},
decays: {
hack: 0,
str: 0.8,
def: 0.8,
dex: 0.8,
agi: 0.8,
cha: 0,
int: 0.75,
},
isKill: true,
});
BlackOperations["Operation Juggernaut"] = new BlackOperation({
name: "Operation Juggernaut",
baseDifficulty: 10e3,
reqdRank: 20e3,
rankGain: 300,
rankLoss: 40,
hpLoss: 300,
weights: {
hack: 0,
str: 0.25,
def: 0.25,
dex: 0.25,
agi: 0.25,
cha: 0,
int: 0,
},
decays: {
hack: 0,
str: 0.8,
def: 0.8,
dex: 0.8,
agi: 0.8,
cha: 0,
int: 0.75,
},
isKill: true,
});
BlackOperations["Operation Red Dragon"] = new BlackOperation({
name: "Operation Red Dragon",
baseDifficulty: 12.5e3,
reqdRank: 25e3,
rankGain: 500,
rankLoss: 50,
hpLoss: 500,
weights: {
hack: 0.05,
str: 0.2,
def: 0.2,
dex: 0.25,
agi: 0.25,
cha: 0,
int: 0.05,
},
decays: {
hack: 0.6,
str: 0.8,
def: 0.8,
dex: 0.8,
agi: 0.8,
cha: 0,
int: 0.75,
},
isKill: true,
});
BlackOperations["Operation K"] = new BlackOperation({
name: "Operation K",
baseDifficulty: 15e3,
reqdRank: 30e3,
rankGain: 750,
rankLoss: 60,
hpLoss: 1000,
weights: {
hack: 0.05,
str: 0.2,
def: 0.2,
dex: 0.25,
agi: 0.25,
cha: 0,
int: 0.05,
},
decays: {
hack: 0.6,
str: 0.8,
def: 0.8,
dex: 0.8,
agi: 0.8,
cha: 0,
int: 0.75,
},
isKill: true,
});
BlackOperations["Operation Deckard"] = new BlackOperation({
name: "Operation Deckard",
baseDifficulty: 20e3,
reqdRank: 40e3,
rankGain: 1e3,
rankLoss: 75,
hpLoss: 200,
weights: {
hack: 0,
str: 0.24,
def: 0.24,
dex: 0.24,
agi: 0.24,
cha: 0,
int: 0.04,
},
decays: {
hack: 0,
str: 0.8,
def: 0.8,
dex: 0.8,
agi: 0.8,
cha: 0,
int: 0.75,
},
isKill: true,
});
BlackOperations["Operation Tyrell"] = new BlackOperation({
name: "Operation Tyrell",
baseDifficulty: 25e3,
reqdRank: 50e3,
rankGain: 1.5e3,
rankLoss: 100,
hpLoss: 500,
weights: {
hack: 0.1,
str: 0.2,
def: 0.2,
dex: 0.2,
agi: 0.2,
cha: 0,
int: 0.1,
},
decays: {
hack: 0.6,
str: 0.8,
def: 0.8,
dex: 0.8,
agi: 0.8,
cha: 0,
int: 0.75,
},
isKill: true,
});
BlackOperations["Operation Wallace"] = new BlackOperation({
name: "Operation Wallace",
baseDifficulty: 30e3,
reqdRank: 75e3,
rankGain: 2e3,
rankLoss: 150,
hpLoss: 1500,
weights: {
hack: 0,
str: 0.24,
def: 0.24,
dex: 0.24,
agi: 0.24,
cha: 0,
int: 0.04,
},
decays: {
hack: 0.6,
str: 0.8,
def: 0.8,
dex: 0.8,
agi: 0.8,
cha: 0,
int: 0.75,
},
isKill: true,
});
BlackOperations["Operation Shoulder of Orion"] = new BlackOperation({
name: "Operation Shoulder of Orion",
baseDifficulty: 35e3,
reqdRank: 100e3,
rankGain: 2.5e3,
rankLoss: 500,
hpLoss: 1500,
weights: {
hack: 0.1,
str: 0.2,
def: 0.2,
dex: 0.2,
agi: 0.2,
cha: 0,
int: 0.1,
},
decays: {
hack: 0.6,
str: 0.8,
def: 0.8,
dex: 0.8,
agi: 0.8,
cha: 0,
int: 0.75,
},
isStealth: true,
});
BlackOperations["Operation Hyron"] = new BlackOperation({
name: "Operation Hyron",
baseDifficulty: 40e3,
reqdRank: 125e3,
rankGain: 3e3,
rankLoss: 1e3,
hpLoss: 500,
weights: {
hack: 0.1,
str: 0.2,
def: 0.2,
dex: 0.2,
agi: 0.2,
cha: 0,
int: 0.1,
},
decays: {
hack: 0.6,
str: 0.8,
def: 0.8,
dex: 0.8,
agi: 0.8,
cha: 0,
int: 0.75,
},
isKill: true,
});
BlackOperations["Operation Morpheus"] = new BlackOperation({
name: "Operation Morpheus",
baseDifficulty: 45e3,
reqdRank: 150e3,
rankGain: 4e3,
rankLoss: 1e3,
hpLoss: 100,
weights: {
hack: 0.05,
str: 0.15,
def: 0.15,
dex: 0.3,
agi: 0.3,
cha: 0,
int: 0.05,
},
decays: {
hack: 0.6,
str: 0.8,
def: 0.8,
dex: 0.8,
agi: 0.8,
cha: 0,
int: 0.75,
},
isStealth: true,
});
BlackOperations["Operation Ion Storm"] = new BlackOperation({
name: "Operation Ion Storm",
baseDifficulty: 50e3,
reqdRank: 175e3,
rankGain: 5e3,
rankLoss: 1e3,
hpLoss: 5000,
weights: {
hack: 0,
str: 0.24,
def: 0.24,
dex: 0.24,
agi: 0.24,
cha: 0,
int: 0.04,
},
decays: {
hack: 0.6,
str: 0.8,
def: 0.8,
dex: 0.8,
agi: 0.8,
cha: 0,
int: 0.75,
},
isKill: true,
});
BlackOperations["Operation Annihilus"] = new BlackOperation({
name: "Operation Annihilus",
baseDifficulty: 55e3,
reqdRank: 200e3,
rankGain: 7.5e3,
rankLoss: 1e3,
hpLoss: 10e3,
weights: {
hack: 0,
str: 0.24,
def: 0.24,
dex: 0.24,
agi: 0.24,
cha: 0,
int: 0.04,
},
decays: {
hack: 0.6,
str: 0.8,
def: 0.8,
dex: 0.8,
agi: 0.8,
cha: 0,
int: 0.75,
},
isKill: true,
});
BlackOperations["Operation Ultron"] = new BlackOperation({
name: "Operation Ultron",
baseDifficulty: 60e3,
reqdRank: 250e3,
rankGain: 10e3,
rankLoss: 2e3,
hpLoss: 10e3,
weights: {
hack: 0.1,
str: 0.2,
def: 0.2,
dex: 0.2,
agi: 0.2,
cha: 0,
int: 0.1,
},
decays: {
hack: 0.6,
str: 0.8,
def: 0.8,
dex: 0.8,
agi: 0.8,
cha: 0,
int: 0.75,
},
isKill: true,
});
BlackOperations["Operation Centurion"] = new BlackOperation({
name: "Operation Centurion",
baseDifficulty: 70e3,
reqdRank: 300e3,
rankGain: 15e3,
rankLoss: 5e3,
hpLoss: 10e3,
weights: {
hack: 0.1,
str: 0.2,
def: 0.2,
dex: 0.2,
agi: 0.2,
cha: 0,
int: 0.1,
},
decays: {
hack: 0.6,
str: 0.8,
def: 0.8,
dex: 0.8,
agi: 0.8,
cha: 0,
int: 0.75,
},
});
BlackOperations["Operation Vindictus"] = new BlackOperation({
name: "Operation Vindictus",
baseDifficulty: 75e3,
reqdRank: 350e3,
rankGain: 20e3,
rankLoss: 20e3,
hpLoss: 20e3,
weights: {
hack: 0.1,
str: 0.2,
def: 0.2,
dex: 0.2,
agi: 0.2,
cha: 0,
int: 0.1,
},
decays: {
hack: 0.6,
str: 0.8,
def: 0.8,
dex: 0.8,
agi: 0.8,
cha: 0,
int: 0.75,
},
});
BlackOperations["Operation Daedalus"] = new BlackOperation({
name: "Operation Daedalus",
baseDifficulty: 80e3,
reqdRank: 400e3,
rankGain: 40e3,
rankLoss: 10e3,
hpLoss: 100e3,
weights: {
hack: 0.1,
str: 0.2,
def: 0.2,
dex: 0.2,
agi: 0.2,
cha: 0,
int: 0.1,
},
decays: {
hack: 0.6,
str: 0.8,
def: 0.8,
dex: 0.8,
agi: 0.8,
cha: 0,
int: 0.75,
},
});
})();

@ -1,4 +1,5 @@
import { Reviver, Generic_toJSON, Generic_fromJSON } from "../../utils/JSONReviver"; import React from "react";
import { Reviver, Generic_toJSON, Generic_fromJSON } from "../utils/JSONReviver";
import { IBladeburner } from "./IBladeburner"; import { IBladeburner } from "./IBladeburner";
import { IActionIdentifier } from "./IActionIdentifier"; import { IActionIdentifier } from "./IActionIdentifier";
import { ActionIdentifier } from "./ActionIdentifier"; import { ActionIdentifier } from "./ActionIdentifier";
@ -9,7 +10,7 @@ import { BlackOperation } from "./BlackOperation";
import { Operation } from "./Operation"; import { Operation } from "./Operation";
import { Contract } from "./Contract"; import { Contract } from "./Contract";
import { GeneralActions } from "./GeneralActions"; import { GeneralActions } from "./GeneralActions";
import { formatNumber } from "../../utils/StringHelperFunctions"; import { formatNumber } from "../utils/StringHelperFunctions";
import { Skills } from "./Skills"; import { Skills } from "./Skills";
import { Skill } from "./Skill"; import { Skill } from "./Skill";
import { City } from "./City"; import { City } from "./City";
@ -17,21 +18,21 @@ import { IAction } from "./IAction";
import { IPlayer } from "../PersonObjects/IPlayer"; import { IPlayer } from "../PersonObjects/IPlayer";
import { IRouter } from "../ui/Router"; import { IRouter } from "../ui/Router";
import { ConsoleHelpText } from "./data/Help"; import { ConsoleHelpText } from "./data/Help";
import { exceptionAlert } from "../../utils/helpers/exceptionAlert"; import { exceptionAlert } from "../utils/helpers/exceptionAlert";
import { getRandomInt } from "../../utils/helpers/getRandomInt"; import { getRandomInt } from "../utils/helpers/getRandomInt";
import { BladeburnerConstants } from "./data/Constants"; import { BladeburnerConstants } from "./data/Constants";
import { numeralWrapper } from "../ui/numeralFormat"; import { numeralWrapper } from "../ui/numeralFormat";
import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers"; import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers";
import { addOffset } from "../../utils/helpers/addOffset"; import { addOffset } from "../utils/helpers/addOffset";
import { Faction } from "../Faction/Faction"; import { Faction } from "../Faction/Faction";
import { Factions, factionExists } from "../Faction/Factions"; import { Factions, factionExists } from "../Faction/Factions";
import { calculateHospitalizationCost } from "../Hospital/Hospital"; import { calculateHospitalizationCost } from "../Hospital/Hospital";
import { redPillFlag } from "../RedPill"; import { redPillFlag } from "../RedPill";
import { dialogBoxCreate } from "../../utils/DialogBox"; import { dialogBoxCreate } from "../ui/React/DialogBox";
import { Settings } from "../Settings/Settings"; import { Settings } from "../Settings/Settings";
import { Augmentations } from "../Augmentation/Augmentations"; import { Augmentations } from "../Augmentation/Augmentations";
import { AugmentationNames } from "../Augmentation/data/AugmentationNames"; import { AugmentationNames } from "../Augmentation/data/AugmentationNames";
import { getTimestamp } from "../../utils/helpers/getTimestamp"; import { getTimestamp } from "../utils/helpers/getTimestamp";
import { joinFaction } from "../Faction/FactionHelpers"; import { joinFaction } from "../Faction/FactionHelpers";
import { WorkerScript } from "../Netscript/WorkerScript"; import { WorkerScript } from "../Netscript/WorkerScript";
@ -1586,11 +1587,6 @@ export class Bladeburner implements IBladeburner {
create(): void { create(): void {
this.contracts["Tracking"] = new Contract({ this.contracts["Tracking"] = new Contract({
name: "Tracking", name: "Tracking",
desc:
"Identify and locate Synthoids. This contract involves reconnaissance " +
"and information-gathering ONLY. Do NOT engage. Stealth is of the utmost importance.<br><br>" +
"Successfully completing Tracking contracts will slightly improve your Synthoid population estimate for " +
"whatever city you are currently in.",
baseDifficulty: 125, baseDifficulty: 125,
difficultyFac: 1.02, difficultyFac: 1.02,
rewardFac: 1.041, rewardFac: 1.041,
@ -1619,10 +1615,6 @@ export class Bladeburner implements IBladeburner {
}); });
this.contracts["Bounty Hunter"] = new Contract({ this.contracts["Bounty Hunter"] = new Contract({
name: "Bounty Hunter", name: "Bounty Hunter",
desc:
"Hunt down and capture fugitive Synthoids. These Synthoids are wanted alive.<br><br>" +
"Successfully completing a Bounty Hunter contract will lower the population in your " +
"current city, and will also increase its chaos level.",
baseDifficulty: 250, baseDifficulty: 250,
difficultyFac: 1.04, difficultyFac: 1.04,
rewardFac: 1.085, rewardFac: 1.085,
@ -1651,10 +1643,6 @@ export class Bladeburner implements IBladeburner {
}); });
this.contracts["Retirement"] = new Contract({ this.contracts["Retirement"] = new Contract({
name: "Retirement", name: "Retirement",
desc:
"Hunt down and retire (kill) rogue Synthoids.<br><br>" +
"Successfully completing a Retirement contract will lower the population in your current " +
"city, and will also increase its chaos level.",
baseDifficulty: 200, baseDifficulty: 200,
difficultyFac: 1.03, difficultyFac: 1.03,
rewardFac: 1.065, rewardFac: 1.065,
@ -1684,12 +1672,6 @@ export class Bladeburner implements IBladeburner {
this.operations["Investigation"] = new Operation({ this.operations["Investigation"] = new Operation({
name: "Investigation", name: "Investigation",
desc:
"As a field agent, investigate and identify Synthoid " +
"populations, movements, and operations.<br><br>Successful " +
"Investigation ops will increase the accuracy of your " +
"synthoid data.<br><br>" +
"You will NOT lose HP from failed Investigation ops.",
baseDifficulty: 400, baseDifficulty: 400,
difficultyFac: 1.03, difficultyFac: 1.03,
rewardFac: 1.07, rewardFac: 1.07,
@ -1719,11 +1701,6 @@ export class Bladeburner implements IBladeburner {
}); });
this.operations["Undercover Operation"] = new Operation({ this.operations["Undercover Operation"] = new Operation({
name: "Undercover Operation", name: "Undercover Operation",
desc:
"Conduct undercover operations to identify hidden " +
"and underground Synthoid communities and organizations.<br><br>" +
"Successful Undercover ops will increase the accuracy of your synthoid " +
"data.",
baseDifficulty: 500, baseDifficulty: 500,
difficultyFac: 1.04, difficultyFac: 1.04,
rewardFac: 1.09, rewardFac: 1.09,
@ -1754,7 +1731,6 @@ export class Bladeburner implements IBladeburner {
}); });
this.operations["Sting Operation"] = new Operation({ this.operations["Sting Operation"] = new Operation({
name: "Sting Operation", name: "Sting Operation",
desc: "Conduct a sting operation to bait and capture particularly " + "notorious Synthoid criminals.",
baseDifficulty: 650, baseDifficulty: 650,
difficultyFac: 1.04, difficultyFac: 1.04,
rewardFac: 1.095, rewardFac: 1.095,
@ -1785,10 +1761,6 @@ export class Bladeburner implements IBladeburner {
}); });
this.operations["Raid"] = new Operation({ this.operations["Raid"] = new Operation({
name: "Raid", name: "Raid",
desc:
"Lead an assault on a known Synthoid community. Note that " +
"there must be an existing Synthoid community in your current city " +
"in order for this Operation to be successful.",
baseDifficulty: 800, baseDifficulty: 800,
difficultyFac: 1.045, difficultyFac: 1.045,
rewardFac: 1.1, rewardFac: 1.1,
@ -1819,10 +1791,6 @@ export class Bladeburner implements IBladeburner {
}); });
this.operations["Stealth Retirement Operation"] = new Operation({ this.operations["Stealth Retirement Operation"] = new Operation({
name: "Stealth Retirement Operation", name: "Stealth Retirement Operation",
desc:
"Lead a covert operation to retire Synthoids. The " +
"objective is to complete the task without " +
"drawing any attention. Stealth and discretion are key.",
baseDifficulty: 1000, baseDifficulty: 1000,
difficultyFac: 1.05, difficultyFac: 1.05,
rewardFac: 1.11, rewardFac: 1.11,
@ -1854,10 +1822,6 @@ export class Bladeburner implements IBladeburner {
}); });
this.operations["Assassination"] = new Operation({ this.operations["Assassination"] = new Operation({
name: "Assassination", name: "Assassination",
desc:
"Assassinate Synthoids that have been identified as " +
"important, high-profile social and political leaders " +
"in the Synthoid communities.",
baseDifficulty: 1500, baseDifficulty: 1500,
difficultyFac: 1.06, difficultyFac: 1.06,
rewardFac: 1.14, rewardFac: 1.14,
@ -1900,7 +1864,7 @@ export class Bladeburner implements IBladeburner {
if (this.action.type !== ActionTypes["Idle"]) { if (this.action.type !== ActionTypes["Idle"]) {
let msg = "Your Bladeburner action was cancelled because you started doing something else."; let msg = "Your Bladeburner action was cancelled because you started doing something else.";
if (this.automateEnabled) { if (this.automateEnabled) {
msg += `<br><br>Your automation was disabled as well. You will have to re-enable it through the Bladeburner console`; msg += `<br /><br />Your automation was disabled as well. You will have to re-enable it through the Bladeburner console`;
this.automateEnabled = false; this.automateEnabled = false;
} }
if (!Settings.SuppressBladeburnerPopup) { if (!Settings.SuppressBladeburnerPopup) {

@ -1,7 +1,7 @@
import { BladeburnerConstants } from "./data/Constants"; import { BladeburnerConstants } from "./data/Constants";
import { getRandomInt } from "../../utils/helpers/getRandomInt"; import { getRandomInt } from "../utils/helpers/getRandomInt";
import { Generic_fromJSON, Generic_toJSON, Reviver } from "../../utils/JSONReviver"; import { Generic_fromJSON, Generic_toJSON, Reviver } from "../utils/JSONReviver";
import { addOffset } from "../../utils/helpers/addOffset"; import { addOffset } from "../utils/helpers/addOffset";
interface IChangePopulationByCountParams { interface IChangePopulationByCountParams {
estChange: number; estChange: number;

@ -1,6 +1,6 @@
import { IBladeburner } from "./IBladeburner"; import { IBladeburner } from "./IBladeburner";
import { Action, IActionParams } from "./Action"; import { Action, IActionParams } from "./Action";
import { Generic_fromJSON, Generic_toJSON, Reviver } from "../../utils/JSONReviver"; import { Generic_fromJSON, Generic_toJSON, Reviver } from "../utils/JSONReviver";
export class Contract extends Action { export class Contract extends Action {
constructor(params: IActionParams | null = null) { constructor(params: IActionParams | null = null) {

@ -1,54 +0,0 @@
import { Action } from "./Action";
import { IMap } from "../types";
export const GeneralActions: IMap<Action> = {};
(function () {
// General Actions
let actionName;
actionName = "Training";
GeneralActions[actionName] = new Action({
name: actionName,
desc:
"Improve your abilities at the Bladeburner unit's specialized training " +
"center. Doing this gives experience for all combat stats and also " +
"increases your max stamina.",
});
actionName = "Field Analysis";
GeneralActions[actionName] = new Action({
name: actionName,
desc:
"Mine and analyze Synthoid-related data. This improves the " +
"Bladeburner's unit intelligence on Synthoid locations and " +
"activities. Completing this action will improve the accuracy " +
"of your Synthoid population estimated in the current city.<br><br>" +
"Does NOT require stamina.",
});
actionName = "Recruitment";
GeneralActions[actionName] = new Action({
name: actionName,
desc:
"Attempt to recruit members for your Bladeburner team. These members " +
"can help you conduct operations.<br><br>" +
"Does NOT require stamina.",
});
actionName = "Diplomacy";
GeneralActions[actionName] = new Action({
name: actionName,
desc:
"Improve diplomatic relations with the Synthoid population. " +
"Completing this action will reduce the Chaos level in your current city.<br><br>" +
"Does NOT require stamina.",
});
actionName = "Hyperbolic Regeneration Chamber";
GeneralActions[actionName] = new Action({
name: actionName,
desc:
"Enter cryogenic stasis using the Bladeburner division's hi-tech Regeneration Chamber. " +
"This will slowly heal your wounds and slightly increase your stamina.<br><br>",
});
})();

@ -0,0 +1,34 @@
import React from "react";
import { Action } from "./Action";
import { IMap } from "../types";
export const GeneralActions: IMap<Action> = {};
(function () {
// General Actions
let actionName;
actionName = "Training";
GeneralActions[actionName] = new Action({
name: actionName,
});
actionName = "Field Analysis";
GeneralActions[actionName] = new Action({
name: actionName,
});
actionName = "Recruitment";
GeneralActions[actionName] = new Action({
name: actionName,
});
actionName = "Diplomacy";
GeneralActions[actionName] = new Action({
name: actionName,
});
actionName = "Hyperbolic Regeneration Chamber";
GeneralActions[actionName] = new Action({
name: actionName,
});
})();

@ -18,7 +18,6 @@ export interface ISuccessChanceParams {
export interface IAction { export interface IAction {
name: string; name: string;
desc: string;
// Difficulty scales with level. See getDifficulty() method // Difficulty scales with level. See getDifficulty() method
level: number; level: number;

@ -1,7 +1,7 @@
import { IBladeburner } from "./IBladeburner"; import { IBladeburner } from "./IBladeburner";
import { BladeburnerConstants } from "./data/Constants"; import { BladeburnerConstants } from "./data/Constants";
import { Action, IActionParams } from "./Action"; import { Action, IActionParams } from "./Action";
import { Generic_fromJSON, Generic_toJSON, Reviver } from "../../utils/JSONReviver"; import { Generic_fromJSON, Generic_toJSON, Reviver } from "../utils/JSONReviver";
export interface IOperationParams extends IActionParams { export interface IOperationParams extends IActionParams {
reqdRank?: number; reqdRank?: number;

@ -0,0 +1,299 @@
import React from "react";
interface IBlackOp {
desc: JSX.Element;
}
export const BlackOperations: {
[key: string]: IBlackOp | undefined;
} = {
"Operation Typhoon": {
desc: (
<>
Obadiah Zenyatta is the leader of a RedWater PMC. It has long been known among the intelligence community that
Zenyatta, along with the rest of the PMC, is a Synthoid.
<br />
<br />
The goal of Operation Typhoon is to find and eliminate Zenyatta and RedWater by any means necessary. After the
task is completed, the actions must be covered up from the general public.
</>
),
},
"Operation Zero": {
desc: (
<>
AeroCorp is one of the world's largest defense contractors. Its leader, Steve Watataki, is thought to be a
supporter of Synthoid rights. He must be removed.
<br />
<br />
The goal of Operation Zero is to covertly infiltrate AeroCorp and uncover any incriminating evidence or
information against Watataki that will cause him to be removed from his position at AeroCorp. Incriminating
evidence can be fabricated as a last resort. Be warned that AeroCorp has some of the most advanced security
measures in the world.
</>
),
},
"Operation X": {
desc: (
<>
We have recently discovered an underground publication group called Samizdat. Even though most of their
publications are nonsensical conspiracy theories, the average human is gullible enough to believe them. Many of
their works discuss Synthoids and pose a threat to society. The publications are spreading rapidly in China and
other Eastern countries.
<br />
<br />
Samizdat has done a good job of keeping hidden and anonymous. However, we've just received intelligence that
their base of operations is in Ishima's underground sewer systems. Your task is to investigate the sewer
systems, and eliminate Samizdat. They must never publish anything again.
</>
),
},
"Operation Titan": {
desc: (
<>
Several months ago Titan Laboratories' Bioengineering department was infiltrated by Synthoids. As far as we
know, Titan Laboratories' management has no knowledge about this. We don't know what the Synthoids are up to,
but the research that they could be conducting using Titan Laboraties' vast resources is potentially very
dangerous.
<br />
<br />
Your goal is to enter and destroy the Bioengineering department's facility in Aevum. The task is not just to
retire the Synthoids there, but also to destroy any information or research at the facility that is relevant to
the Synthoids and their goals.
</>
),
},
"Operation Ares": {
desc: (
<>
One of our undercover agents, Agent Carter, has informed us of a massive weapons deal going down in Dubai
between rogue Russian militants and a radical Synthoid community. These weapons are next-gen plasma and energy
weapons. It is critical for the safety of humanity that this deal does not happen.
<br />
<br />
Your task is to intercept the deal. Leave no survivors.
</>
),
},
"Operation Archangel": {
desc: (
<>
Our analysts have discovered that the popular Red Rabbit brothel in Amsterdam is run and 'staffed' by MK-VI
Synthoids. Intelligence suggests that the profit from this brothel is used to fund a large black market arms
trafficking operation.
<br />
<br />
The goal of this operation is to take out the leaders that are running the Red Rabbit brothel. Try to limit the
number of other casualties, but do what you must to complete the mission.
</>
),
},
"Operation Juggernaut": {
desc: (
<>
The CIA has just encountered a new security threat. A new criminal group, lead by a shadowy operative who calls
himself Juggernaut, has been smuggling drugs and weapons (including suspected bioweapons) into Sector-12. We
also have reason to believe the tried to break into one of Universal Energy's facilities in order to cause a
city-wide blackout. The CIA suspects that Juggernaut is a heavily-augmented Synthoid, and have thus enlisted our
help.
<br />
<br />
Your mission is to eradicate Juggernaut and his followers.
</>
),
},
"Operation Red Dragon": {
desc: (
<>
The Tetrads criminal organization is suspected of reverse-engineering the MK-VI Synthoid design. We believe they
altered and possibly improved the design and began manufacturing their own Synthoid models in order to bolster
their criminal activities.
<br />
<br />
Your task is to infiltrate and destroy the Tetrads' base of operations in Los Angeles. Intelligence tells us
that their base houses one of their Synthoid manufacturing units.
</>
),
},
"Operation K": {
desc: (
<>
CODE RED SITUATION. Our intelligence tells us that VitaLife has discovered a new android cloning technology.
This technology is supposedly capable of cloning Synthoid, not only physically but also their advanced AI
modules. We do not believe that VitaLife is trying to use this technology illegally or maliciously, but if any
Synthoids were able to infiltrate the corporation and take advantage of this technology then the results would
be catastrophic.
<br />
<br />
We do not have the power or jurisdiction to shutdown this down through legal or political means, so we must
resort to a covert operation. Your goal is to destroy this technology and eliminate anyone who was involved in
its creation.
</>
),
},
"Operation Deckard": {
desc: (
<>
Despite your success in eliminating VitaLife's new android-replicating technology in Operation K, we've
discovered that a small group of MK-VI Synthoids were able to make off with the schematics and design of the
technology before the Operation. It is almost a certainty that these Synthoids are some of the rogue MK-VI ones
from the Synthoid Uprising.
<br />
<br />
The goal of Operation Deckard is to hunt down these Synthoids and retire them. I don't need to tell you how
critical this mission is.
</>
),
},
"Operation Tyrell": {
desc: (
<>
A week ago Blade Industries reported a small break-in at one of their Aevum Augmentation storage facitilities.
We figured out that The Dark Army was behind the heist, and didn't think any more of it. However, we've just
discovered that several known MK-VI Synthoids were part of that break-in group.
<br />
<br />
We cannot have Synthoids upgrading their already-enhanced abilities with Augmentations. Your task is to hunt
down the associated Dark Army members and eliminate them.
</>
),
},
"Operation Wallace": {
desc: (
<>
Based on information gathered from Operation Tyrell, we've discovered that The Dark Army was well aware that
there were Synthoids amongst their ranks. Even worse, we believe that The Dark Army is working together with
other criminal organizations such as The Syndicate and that they are planning some sort of large-scale takeover
of multiple major cities, most notably Aevum. We suspect that Synthoids have infiltrated the ranks of these
criminal factions and are trying to stage another Synthoid uprising.
<br />
<br />
The best way to deal with this is to prevent it before it even happens. The goal of Operation Wallace is to
destroy the Dark Army and Syndicate factions in Aevum immediately. Leave no survivors.
</>
),
},
"Operation Shoulder of Orion": {
desc: (
<>
China's Solaris Space Systems is secretly launching the first manned spacecraft in over a decade using
Synthoids. We believe China is trying to establish the first off-world colonies.
<br />
<br />
The mission is to prevent this launch without instigating an international conflict. When you accept this
mission you will be officially disavowed by the NSA and the national government until after you successfully
return. In the event of failure, all of the operation's team members must not let themselves be captured alive.
</>
),
},
"Operation Hyron": {
desc: (
<>
Our intelligence tells us that Fulcrum Technologies is developing a quantum supercomputer using human brains as
core processors. This supercomputer is rumored to be able to store vast amounts of data and perform computations
unmatched by any other supercomputer on the planet. But more importantly, the use of organic human brains means
that the supercomputer may be able to reason abstractly and become self-aware.
<br />
<br />
I do not need to remind you why sentient-level AIs pose a serious threat to all of mankind.
<br />
<br />
The research for this project is being conducted at one of Fulcrum Technologies secret facilities in Aevum,
codenamed 'Alpha Ranch'. Infiltrate the compound, delete and destroy the work, and then find and kill the
project lead.
</>
),
},
"Operation Morpheus": {
desc: (
<>
DreamSense Technologies is an advertising company that uses special technology to transmit their ads into the
peoples dreams and subconcious. They do this using broadcast transmitter towers. Based on information from our
agents and informants in Chonqging, we have reason to believe that one of the broadcast towers there has been
compromised by Synthoids and is being used to spread pro-Synthoid propaganda.
<br />
<br />
The mission is to destroy this broadcast tower. Speed and stealth are of the upmost important for this.
</>
),
},
"Operation Ion Storm": {
desc: (
<>
Our analysts have uncovered a gathering of MK-VI Synthoids that have taken up residence in the Sector-12 Slums.
We don't know if they are rogue Synthoids from the Uprising, but we do know that they have been stockpiling
weapons, money, and other resources. This makes them dangerous.
<br />
<br />
This is a full-scale assault operation to find and retire all of these Synthoids in the Sector-12 Slums.
</>
),
},
"Operation Annihilus": {
desc: (
<>
Our superiors have ordered us to eradicate everything and everyone in an underground facility located in Aevum.
They tell us that the facility houses many dangerous Synthoids and belongs to a terrorist organization called
'The Covenant'. We have no prior intelligence about this organization, so you are going in blind.
</>
),
},
"Operation Ultron": {
desc: (
<>
OmniTek Incorporated, the original designer and manufacturer of Synthoids, has notified us of a malfunction in
their AI design. This malfunction, when triggered, causes MK-VI Synthoids to become radicalized and seek out the
destruction of humanity. They say that this bug affects all MK-VI Synthoids, not just the rogue ones from the
Uprising.
<br />
<br />
OmniTek has also told us they they believe someone has triggered this malfunction in a large group of MK-VI
Synthoids, and that these newly-radicalized Synthoids are now amassing in Volhaven to form a terrorist group
called Ultron.
<br />
<br />
Intelligence suggests Ultron is heavily armed and that their members are augmented. We believe Ultron is making
moves to take control of and weaponize DeltaOne's Tactical High-Energy Satellite Laser Array (THESLA).
<br />
<br />
Your task is to find and destroy Ultron.
</>
),
},
"Operation Centurion": {
desc: (
<>
{"D)@#)($M)C0293c40($*)@#D0JUMP3Rm0C<*@#)*$)#02c94830c(#$*D)"}
<br />
<br />
Throughout all of humanity's history, we have relied on technology to survive, conquer, and progress. Its
advancement became our primary goal. And at the peak of human civilization technology turned into power. Global,
absolute power.
<br />
<br />
It seems that the universe is not without a sense of irony.
<br />
<br />
{"D)@#)($M)C0293c40($*)@#D0JUMP3Rm0C<*@#)*$)#02c94830c(#$*D)"}
</>
),
},
"Operation Vindictus": {
desc: (
<>
{"D)@#)($M)C0293c40($*)@#D0JUMP3Rm0C<*@#)*$)#02c94830c(#$*D)"}
<br />
<br />
The bits are all around us. The daemons that hold the Node together can manifest themselves in many different
ways.
<br />
<br />
{"D)@#)($M)C0293c40($*)@#D0JUMP3Rm0C<*@#)*$)#02c94830c(#$*D)"}
</>
),
},
"Operation Daedalus": {
desc: <> Yesterday we obeyed kings and bent our neck to emperors. Today we kneel only to truth.</>,
},
};

@ -0,0 +1,44 @@
import React from "react";
interface IContract {
desc: JSX.Element;
}
export const Contracts: {
[key: string]: IContract | undefined;
} = {
Tracking: {
desc: (
<>
Identify and locate Synthoids. This contract involves reconnaissance and information-gathering ONLY. Do NOT
engage. Stealth is of the utmost importance.
<br />
<br />
Successfully completing Tracking contracts will slightly improve your Synthoid population estimate for whatever
city you are currently in.
</>
),
},
"Bounty Hunter": {
desc: (
<>
Hunt down and capture fugitive Synthoids. These Synthoids are wanted alive.
<br />
<br />
Successfully completing a Bounty Hunter contract will lower the population in your current city, and will also
increase its chaos level.
</>
),
},
Retirement: {
desc: (
<>
Hunt down and retire (kill) rogue Synthoids.
<br />
<br />
Successfully completing a Retirement contract will lower the population in your current city, and will also
increase its chaos level.
</>
),
},
};

@ -0,0 +1,65 @@
import React from "react";
interface IContract {
desc: JSX.Element;
}
export const GeneralActions: {
[key: string]: IContract | undefined;
} = {
Training: {
desc: (
<>
Improve your abilities at the Bladeburner unit's specialized training center. Doing this gives experience for
all combat stats and also increases your max stamina.
</>
),
},
"Field Analysis": {
desc: (
<>
Mine and analyze Synthoid-related data. This improves the Bladeburner's unit intelligence on Synthoid locations
and activities. Completing this action will improve the accuracy of your Synthoid population estimated in the
current city.
<br />
<br />
Does NOT require stamina.
</>
),
},
Recruitment: {
desc: (
<>
Attempt to recruit members for your Bladeburner team. These members can help you conduct operations.
<br />
<br />
Does NOT require stamina.
</>
),
},
Diplomacy: {
desc: (
<>
Improve diplomatic relations with the Synthoid population. Completing this action will reduce the Chaos level in
your current city.
<br />
<br />
Does NOT require stamina.
</>
),
},
"Hyperbolic Regeneration Chamber": {
desc: (
<>
Enter cryogenic stasis using the Bladeburner division's hi-tech Regeneration Chamber. This will slowly heal your
wounds and slightly increase your stamina.
<br />
<br />
</>
),
},
};

@ -1,4 +1,4 @@
import { getRandomInt } from "../../../utils/helpers/getRandomInt"; import { getRandomInt } from "../../utils/helpers/getRandomInt";
export const Growths: { export const Growths: {
[key: string]: (() => number) | undefined; [key: string]: (() => number) | undefined;

@ -0,0 +1,60 @@
import React from "react";
interface IOperation {
desc: JSX.Element;
}
export const Operations: {
[key: string]: IOperation | undefined;
} = {
Investigation: {
desc: (
<>
As a field agent, investigate and identify Synthoid populations, movements, and operations.
<br />
<br />
Successful Investigation ops will increase the accuracy of your synthoid data.
<br />
<br />
You will NOT lose HP from failed Investigation ops.
</>
),
},
"Undercover Operation": {
desc: (
<>
Conduct undercover operations to identify hidden and underground Synthoid communities and organizations.
<br />
<br />
Successful Undercover ops will increase the accuracy of your synthoid data.
</>
),
},
"Sting Operation": {
desc: <>Conduct a sting operation to bait and capture particularly notorious Synthoid criminals.</>,
},
Raid: {
desc: (
<>
Lead an assault on a known Synthoid community. Note that there must be an existing Synthoid community in your
current city in order for this Operation to be successful.
</>
),
},
"Stealth Retirement Operation": {
desc: (
<>
Lead a covert operation to retire Synthoids. The objective is to complete the task without drawing any
attention. Stealth and discretion are key.
</>
),
},
Assassination: {
desc: (
<>
Assassinate Synthoids that have been identified as important, high-profile social and political leaders in the
Synthoid communities.
</>
),
},
};

@ -0,0 +1,75 @@
import React from "react";
import { IAction } from "../IAction";
import { IBladeburner } from "../IBladeburner";
import { BladeburnerConstants } from "../data/Constants";
import { use } from "../../ui/Context";
import Typography from "@mui/material/Typography";
import Tooltip from "@mui/material/Tooltip";
import Box from "@mui/material/Box";
import IconButton from "@mui/material/IconButton";
import ArrowDropUpIcon from "@mui/icons-material/ArrowDropUp";
import ArrowDropDownIcon from "@mui/icons-material/ArrowDropDown";
interface IProps {
action: IAction;
isActive: boolean;
bladeburner: IBladeburner;
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);
rerender();
}
function decreaseLevel(): void {
if (!canDecrease) return;
--action.level;
if (isActive) bladeburner.startAction(player, bladeburner.action);
rerender();
}
return (
<Box display="flex" flexDirection="row" alignItems="center">
<Box display="flex">
<Tooltip
title={
<Typography>
{action.getSuccessesNeededForNextLevel(BladeburnerConstants.ContractSuccessesPerLevel)} successes needed
for next level
</Typography>
}
>
<Typography>
Level: {action.level} / {action.maxLevel}
</Typography>
</Tooltip>
</Box>
<Tooltip
disableInteractive
title={isActive ? <Typography>WARNING: changing the level will restart the Operation</Typography> : ""}
>
<IconButton disabled={!canIncrease} onClick={increaseLevel}>
<ArrowDropUpIcon />
</IconButton>
</Tooltip>
<Tooltip
disableInteractive
title={isActive ? <Typography>WARNING: changing the level will restart the Operation</Typography> : ""}
>
<IconButton disabled={!canDecrease} onClick={decreaseLevel}>
<ArrowDropDownIcon />
</IconButton>
</Tooltip>
</Box>
);
}

@ -1,54 +1,45 @@
import React, { useState, useEffect } from "react"; import React from "react";
import { GeneralActionPage } from "./GeneralActionPage"; import { GeneralActionPage } from "./GeneralActionPage";
import { ContractPage } from "./ContractPage"; import { ContractPage } from "./ContractPage";
import { OperationPage } from "./OperationPage"; import { OperationPage } from "./OperationPage";
import { BlackOpPage } from "./BlackOpPage"; import { BlackOpPage } from "./BlackOpPage";
import { SkillPage } from "./SkillPage"; import { SkillPage } from "./SkillPage";
import { stealthIcon, killIcon } from "../data/Icons";
import { IBladeburner } from "../IBladeburner"; import { IBladeburner } from "../IBladeburner";
import { IPlayer } from "../../PersonObjects/IPlayer"; import { IPlayer } from "../../PersonObjects/IPlayer";
import Tabs from "@mui/material/Tabs";
import Tab from "@mui/material/Tab";
import Box from "@mui/material/Box";
import Paper from "@mui/material/Paper";
interface IProps { interface IProps {
bladeburner: IBladeburner; bladeburner: IBladeburner;
player: IPlayer; player: IPlayer;
} }
export function AllPages(props: IProps): React.ReactElement { export function AllPages(props: IProps): React.ReactElement {
const [page, setPage] = useState("General"); const [value, setValue] = React.useState(0);
const setRerender = useState(false)[1];
useEffect(() => { function handleChange(event: React.SyntheticEvent, tab: number): void {
const id = setInterval(() => setRerender((old) => !old), 1000); setValue(tab);
return () => clearInterval(id);
}, []);
function Header(props: { name: string }): React.ReactElement {
return (
<a
onClick={() => setPage(props.name)}
className={page !== props.name ? "bladeburner-nav-button noselect" : "bladeburner-nav-button-inactive noselect"}
>
{props.name}
</a>
);
} }
return ( return (
<> <>
<Header name={"General"} /> <Tabs variant="fullWidth" value={value} onChange={handleChange}>
<Header name={"Contracts"} /> <Tab label="General" />
<Header name={"Operations"} /> <Tab label="Contracts" />
<Header name={"BlackOps"} /> <Tab label="Operations" />
<Header name={"Skills"} /> <Tab label="BlackOps" />
<div style={{ display: "block", margin: "4px", padding: "4px" }}> <Tab label="Skills" />
{page === "General" && <GeneralActionPage bladeburner={props.bladeburner} player={props.player} />} </Tabs>
{page === "Contracts" && <ContractPage bladeburner={props.bladeburner} player={props.player} />} <Box sx={{ p: 1 }}>
{page === "Operations" && <OperationPage bladeburner={props.bladeburner} player={props.player} />} {value === 0 && <GeneralActionPage bladeburner={props.bladeburner} player={props.player} />}
{page === "BlackOps" && <BlackOpPage bladeburner={props.bladeburner} player={props.player} />} {value === 1 && <ContractPage bladeburner={props.bladeburner} player={props.player} />}
{page === "Skills" && <SkillPage bladeburner={props.bladeburner} />} {value === 2 && <OperationPage bladeburner={props.bladeburner} player={props.player} />}
</div> {value === 3 && <BlackOpPage bladeburner={props.bladeburner} player={props.player} />}
<span className="text"> {value === 4 && <SkillPage bladeburner={props.bladeburner} />}
{stealthIcon} = This action requires stealth, {killIcon} = This action involves retirement </Box>
</span>
</> </>
); );
} }

@ -0,0 +1,26 @@
import React from "react";
import { IAction } from "../IAction";
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;
rerender: () => void;
}
export function Autolevel(props: IProps): React.ReactElement {
function onAutolevel(event: React.ChangeEvent<HTMLInputElement>): void {
props.action.autoLevel = event.target.checked;
props.rerender();
}
return (
<Box display="flex" flexDirection="row" alignItems="center">
<Tooltip title={<Typography>Automatically increase operation level when possible</Typography>}>
<Typography> Autolevel:</Typography>
</Tooltip>
<Switch checked={props.action.autoLevel} onChange={onAutolevel} />
</Box>
);
}

@ -1,23 +1,30 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { formatNumber, convertTimeMsToTimeElapsedString } from "../../../utils/StringHelperFunctions"; import { formatNumber, convertTimeMsToTimeElapsedString } from "../../utils/StringHelperFunctions";
import { ActionTypes } from "../data/ActionTypes"; import { ActionTypes } from "../data/ActionTypes";
import { createProgressBarText } from "../../../utils/helpers/createProgressBarText"; import { createProgressBarText } from "../../utils/helpers/createProgressBarText";
import { stealthIcon, killIcon } from "../data/Icons"; import { TeamSizeButton } from "./TeamSizeButton";
import { createPopup } from "../../ui/React/createPopup";
import { TeamSizePopup } from "./TeamSizePopup";
import { IBladeburner } from "../IBladeburner"; import { IBladeburner } from "../IBladeburner";
import { BlackOperation } from "../BlackOperation";
import { BlackOperations } from "../data/BlackOperations";
import { IPlayer } from "../../PersonObjects/IPlayer"; import { IPlayer } from "../../PersonObjects/IPlayer";
import { SuccessChance } from "./SuccessChance";
import { CopyableText } from "../../ui/React/CopyableText"; import { CopyableText } from "../../ui/React/CopyableText";
import { SuccessChance } from "./SuccessChance";
import { StartButton } from "./StartButton";
import Typography from "@mui/material/Typography";
import Paper from "@mui/material/Paper";
interface IProps { interface IProps {
bladeburner: IBladeburner; bladeburner: IBladeburner;
player: IPlayer; player: IPlayer;
action: any; action: BlackOperation;
} }
export function BlackOpElem(props: IProps): React.ReactElement { export function BlackOpElem(props: IProps): React.ReactElement {
const setRerender = useState(false)[1]; const setRerender = useState(false)[1];
function rerender(): void {
setRerender((old) => !old);
}
const isCompleted = props.bladeburner.blackops[props.action.name] != null; const isCompleted = props.bladeburner.blackops[props.action.name] != null;
if (isCompleted) { if (isCompleted) {
return <h2 style={{ display: "block" }}>{props.action.name} (COMPLETED)</h2>; return <h2 style={{ display: "block" }}>{props.action.name} (COMPLETED)</h2>;
@ -26,7 +33,6 @@ export function BlackOpElem(props: IProps): React.ReactElement {
const isActive = const isActive =
props.bladeburner.action.type === ActionTypes["BlackOperation"] && props.bladeburner.action.type === ActionTypes["BlackOperation"] &&
props.action.name === props.bladeburner.action.name; props.action.name === props.bladeburner.action.name;
const estimatedSuccessChance = props.action.getEstSuccessChance(props.bladeburner);
const actionTime = props.action.getActionTime(props.bladeburner); const actionTime = props.action.getActionTime(props.bladeburner);
const hasReqdRank = props.bladeburner.rank >= props.action.reqdRank; const hasReqdRank = props.bladeburner.rank >= props.action.reqdRank;
const computedActionTimeCurrent = Math.min( const computedActionTimeCurrent = Math.min(
@ -34,70 +40,54 @@ export function BlackOpElem(props: IProps): React.ReactElement {
props.bladeburner.actionTimeToComplete, props.bladeburner.actionTimeToComplete,
); );
function onStart(): void { const actionData = BlackOperations[props.action.name];
props.bladeburner.action.type = ActionTypes.BlackOperation; if (actionData === undefined) {
props.bladeburner.action.name = props.action.name; throw new Error(`Cannot find data for ${props.action.name}`);
props.bladeburner.startAction(props.player, props.bladeburner.action);
setRerender((old) => !old);
}
function onTeam(): void {
const popupId = "bladeburner-operation-set-team-size-popup";
createPopup(popupId, TeamSizePopup, {
bladeburner: props.bladeburner,
action: props.action,
popupId: popupId,
});
} }
return ( return (
<> <Paper sx={{ my: 1, p: 1 }}>
<h2 style={{ display: "inline-block" }}> <Typography>
{isActive ? ( {isActive ? (
<> <>
<CopyableText value={props.action.name} /> (IN PROGRESS - {formatNumber(computedActionTimeCurrent, 0)} /{" "} <>
{formatNumber(props.bladeburner.actionTimeToComplete, 0)}) <CopyableText value={props.action.name} /> (IN PROGRESS - {formatNumber(computedActionTimeCurrent, 0)} /{" "}
{formatNumber(props.bladeburner.actionTimeToComplete, 0)})
<p style={{ display: "block" }}>
{createProgressBarText({
progress: computedActionTimeCurrent / props.bladeburner.actionTimeToComplete,
})}
</p>
</>
</> </>
) : ( ) : (
<CopyableText value={props.action.name} /> <>
<CopyableText value={props.action.name} />
<StartButton
bladeburner={props.bladeburner}
type={ActionTypes.BlackOperation}
name={props.action.name}
rerender={rerender}
/>
<TeamSizeButton action={props.action} bladeburner={props.bladeburner} />
</>
)} )}
</h2> </Typography>
{isActive ? (
<p style={{ display: "block" }}>
{createProgressBarText({
progress: computedActionTimeCurrent / props.bladeburner.actionTimeToComplete,
})}
</p>
) : (
<>
<a
className={hasReqdRank ? "a-link-button" : "a-link-button-inactive"}
style={{ margin: "3px", padding: "3px" }}
onClick={onStart}
>
Start
</a>
<a onClick={onTeam} style={{ margin: "3px", padding: "3px" }} className="a-link-button">
Set Team Size (Curr Size: {formatNumber(props.action.teamCount, 0)})
</a>
</>
)}
<br /> <br />
<br /> <br />
<p style={{ display: "inline-block" }} dangerouslySetInnerHTML={{ __html: props.action.desc }} /> <Typography>{actionData.desc}</Typography>
<br /> <br />
<br /> <br />
<p style={{ display: "block", color: hasReqdRank ? "white" : "red" }}> <Typography color={hasReqdRank ? "primary" : "error"}>
Required Rank: {formatNumber(props.action.reqdRank, 0)} Required Rank: {formatNumber(props.action.reqdRank, 0)}
</p> </Typography>
<br /> <br />
<pre style={{ display: "inline-block" }}> <Typography>
Estimated Success Chance: <SuccessChance chance={estimatedSuccessChance} />{" "} <SuccessChance action={props.action} bladeburner={props.bladeburner} />
{props.action.isStealth ? stealthIcon : <></>}
{props.action.isKill ? killIcon : <></>}
<br /> <br />
Time Required: {convertTimeMsToTimeElapsedString(actionTime * 1000)} Time Required: {convertTimeMsToTimeElapsedString(actionTime * 1000)}
</pre> </Typography>
</> </Paper>
); );
} }

@ -35,9 +35,7 @@ export function BlackOpList(props: IProps): React.ReactElement {
return ( return (
<> <>
{blackops.map((blackop: BlackOperation) => ( {blackops.map((blackop: BlackOperation) => (
<li key={blackop.name} className="bladeburner-action"> <BlackOpElem key={blackop.name} bladeburner={props.bladeburner} action={blackop} player={props.player} />
<BlackOpElem bladeburner={props.bladeburner} action={blackop} player={props.player} />
</li>
))} ))}
</> </>
); );

@ -2,6 +2,7 @@ import * as React from "react";
import { BlackOpList } from "./BlackOpList"; import { BlackOpList } from "./BlackOpList";
import { IBladeburner } from "../IBladeburner"; import { IBladeburner } from "../IBladeburner";
import { IPlayer } from "../../PersonObjects/IPlayer"; import { IPlayer } from "../../PersonObjects/IPlayer";
import Typography from "@mui/material/Typography";
interface IProps { interface IProps {
bladeburner: IBladeburner; bladeburner: IBladeburner;
@ -11,7 +12,7 @@ interface IProps {
export function BlackOpPage(props: IProps): React.ReactElement { export function BlackOpPage(props: IProps): React.ReactElement {
return ( return (
<> <>
<p style={{ display: "block", margin: "4px", padding: "4px" }}> <Typography>
Black Operations (Black Ops) are special, one-time covert operations. Each Black Op must be unlocked Black Operations (Black Ops) are special, one-time covert operations. Each Black Op must be unlocked
successively by completing the one before it. successively by completing the one before it.
<br /> <br />
@ -21,7 +22,7 @@ export function BlackOpPage(props: IProps): React.ReactElement {
<br /> <br />
Like normal operations, you may use a team for Black Ops. Failing a black op will incur heavy HP and rank Like normal operations, you may use a team for Black Ops. Failing a black op will incur heavy HP and rank
losses. losses.
</p> </Typography>
<BlackOpList bladeburner={props.bladeburner} player={props.player} /> <BlackOpList bladeburner={props.bladeburner} player={props.player} />
</> </>
); );

@ -1,7 +1,7 @@
import React from "react"; import React from "react";
import { use } from "../../ui/Context"; import { use } from "../../ui/Context";
import { CinematicText } from "../../ui/React/CinematicText"; import { CinematicText } from "../../ui/React/CinematicText";
import { dialogBoxCreate } from "../../../utils/DialogBox"; import { dialogBoxCreate } from "../../ui/React/DialogBox";
export function BladeburnerCinematic(): React.ReactElement { export function BladeburnerCinematic(): React.ReactElement {
const router = use.Router(); const router = use.Router();

@ -1,42 +1,39 @@
import React from "react"; import React, { useState, useEffect } from "react";
import { Stats } from "./Stats"; import { Stats } from "./Stats";
import { Console } from "./Console"; import { Console } from "./Console";
import { AllPages } from "./AllPages"; import { AllPages } from "./AllPages";
import { use } from "../../ui/Context"; import { use } from "../../ui/Context";
import Grid from "@mui/material/Grid";
import Box from "@mui/material/Box";
export function BladeburnerRoot(): React.ReactElement { export function BladeburnerRoot(): React.ReactElement {
const player = use.Player(); const player = use.Player();
const router = use.Router(); const router = use.Router();
const setRerender = useState(false)[1];
function rerender(): void {
setRerender((old) => !old);
}
useEffect(() => {
const id = setInterval(rerender, 200);
return () => clearInterval(id);
}, []);
const bladeburner = player.bladeburner; const bladeburner = player.bladeburner;
if (bladeburner === null) return <></>; if (bladeburner === null) return <></>;
return ( return (
<div className="bladeburner-container"> <Box display="flex" flexDirection="column">
<div style={{ height: "60%", display: "block", position: "relative" }}> <Grid container>
<div <Grid item xs={6}>
style={{
height: "100%",
width: "30%",
display: "inline-block",
border: "1px solid white",
}}
>
<Stats bladeburner={bladeburner} player={player} router={router} /> <Stats bladeburner={bladeburner} player={player} router={router} />
</div> </Grid>
<Console bladeburner={bladeburner} player={player} /> <Grid item xs={6}>
</div> <Console bladeburner={bladeburner} player={player} />
<div </Grid>
style={{ </Grid>
width: "70%",
display: "block", <AllPages bladeburner={bladeburner} player={player} />
border: "1px solid white", </Box>
marginTop: "6px",
padding: "6px",
position: "relative",
}}
>
<AllPages bladeburner={bladeburner} player={player} />
</div>
</div>
); );
} }

@ -2,18 +2,48 @@ import React, { useState, useRef, useEffect } from "react";
import { IBladeburner } from "../IBladeburner"; import { IBladeburner } from "../IBladeburner";
import { IPlayer } from "../../PersonObjects/IPlayer"; import { IPlayer } from "../../PersonObjects/IPlayer";
import Paper from "@mui/material/Paper";
import List from "@mui/material/List";
import ListItem from "@mui/material/ListItem";
import Box from "@mui/material/Box";
import Typography from "@mui/material/Typography";
import TextField from "@mui/material/TextField";
import { Theme } from "@mui/material/styles";
import makeStyles from "@mui/styles/makeStyles";
import createStyles from "@mui/styles/createStyles";
interface ILineProps { interface ILineProps {
content: any; content: any;
} }
const useStyles = makeStyles((theme: Theme) =>
createStyles({
textfield: {
margin: theme.spacing(0),
width: "100%",
},
input: {
backgroundColor: "#000",
},
nopadding: {
padding: theme.spacing(0),
},
preformatted: {
whiteSpace: "pre-wrap",
margin: theme.spacing(0),
},
list: {
padding: theme.spacing(0),
height: "100%",
},
}),
);
function Line(props: ILineProps): React.ReactElement { function Line(props: ILineProps): React.ReactElement {
return ( return (
<tr> <ListItem sx={{ p: 0 }}>
<td className="bladeburner-console-line" style={{ color: "var(--my-font-color)", whiteSpace: "pre-wrap" }}> <Typography>{props.content}</Typography>
{props.content} </ListItem>
</td>
</tr>
); );
} }
@ -23,15 +53,21 @@ interface IProps {
} }
export function Console(props: IProps): React.ReactElement { export function Console(props: IProps): React.ReactElement {
const lastRef = useRef<HTMLDivElement>(null); const classes = useStyles();
const scrollHook = useRef<HTMLDivElement>(null);
const [command, setCommand] = useState("");
const setRerender = useState(false)[1]; const setRerender = useState(false)[1];
function handleCommandChange(event: React.ChangeEvent<HTMLInputElement>): void {
setCommand(event.target.value);
}
const [consoleHistoryIndex, setConsoleHistoryIndex] = useState(props.bladeburner.consoleHistory.length); const [consoleHistoryIndex, setConsoleHistoryIndex] = useState(props.bladeburner.consoleHistory.length);
// TODO: Figure out how to actually make the scrolling work correctly. // TODO: Figure out how to actually make the scrolling work correctly.
function scrollToBottom(): void { function scrollToBottom(): void {
if (!lastRef.current) return; if (!scrollHook.current) return;
lastRef.current.scrollTop = lastRef.current.scrollHeight; scrollHook.current.scrollTop = scrollHook.current.scrollHeight;
} }
function rerender(): void { function rerender(): void {
@ -50,13 +86,11 @@ export function Console(props: IProps): React.ReactElement {
function handleKeyDown(event: React.KeyboardEvent<HTMLInputElement>): void { function handleKeyDown(event: React.KeyboardEvent<HTMLInputElement>): void {
if (event.keyCode === 13) { if (event.keyCode === 13) {
event.preventDefault(); event.preventDefault();
const command = event.currentTarget.value;
event.currentTarget.value = "";
if (command.length > 0) { if (command.length > 0) {
props.bladeburner.postToConsole("> " + command); props.bladeburner.postToConsole("> " + command);
props.bladeburner.executeConsoleCommands(props.player, command); props.bladeburner.executeConsoleCommands(props.player, command);
setConsoleHistoryIndex(props.bladeburner.consoleHistory.length); setConsoleHistoryIndex(props.bladeburner.consoleHistory.length);
rerender(); setCommand("");
} }
} }
@ -105,31 +139,35 @@ export function Console(props: IProps): React.ReactElement {
} }
return ( return (
<div ref={lastRef} className="bladeburner-console-div"> <Box height={"60vh"} display={"flex"} alignItems={"stretch"} component={Paper}>
<table className="bladeburner-console-table"> <Box>
<tbody> <List sx={{ height: "100%", overflow: "auto" }}>
{/*
TODO: optimize this.
using `i` as a key here isn't great because it'll re-render everything
everytime the console reaches max length.
*/}
{props.bladeburner.consoleLogs.map((log: any, i: number) => ( {props.bladeburner.consoleLogs.map((log: any, i: number) => (
<Line key={i} content={log} /> <Line key={i} content={log} />
))} ))}
<tr key="input" id="bladeburner-console-input-row" className="bladeburner-console-input-row"> <TextField
<td className="bladeburner-console-input-cell"> classes={{ root: classes.textfield }}
<pre>{"> "}</pre> autoFocus
<input variant="standard"
autoFocus tabIndex={1}
className="bladeburner-console-input" type="text"
tabIndex={1} value={command}
type="text" onChange={handleCommandChange}
onKeyDown={handleKeyDown} onKeyDown={handleKeyDown}
/> InputProps={{
</td> // for players to hook in
</tr> className: classes.input,
</tbody> startAdornment: (
</table> <>
</div> <Typography>&gt;&nbsp;</Typography>
</>
),
spellCheck: false,
}}
/>
</List>
<div ref={scrollHook}></div>
</Box>
</Box>
); );
} }

@ -1,114 +1,79 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { ActionTypes } from "../data/ActionTypes"; import { ActionTypes } from "../data/ActionTypes";
import { createProgressBarText } from "../../../utils/helpers/createProgressBarText"; import { createProgressBarText } from "../../utils/helpers/createProgressBarText";
import { formatNumber, convertTimeMsToTimeElapsedString } from "../../../utils/StringHelperFunctions"; import { formatNumber, convertTimeMsToTimeElapsedString } from "../../utils/StringHelperFunctions";
import { stealthIcon, killIcon } from "../data/Icons"; import { Contracts } from "../data/Contracts";
import { BladeburnerConstants } from "../data/Constants";
import { IBladeburner } from "../IBladeburner"; import { IBladeburner } from "../IBladeburner";
import { IAction } from "../IAction";
import { IPlayer } from "../../PersonObjects/IPlayer"; import { IPlayer } from "../../PersonObjects/IPlayer";
import { SuccessChance } from "./SuccessChance"; import { SuccessChance } from "./SuccessChance";
import { CopyableText } from "../../ui/React/CopyableText"; import { CopyableText } from "../../ui/React/CopyableText";
import { ActionLevel } from "./ActionLevel";
import { Autolevel } from "./Autolevel";
import { StartButton } from "./StartButton";
import Typography from "@mui/material/Typography";
import Paper from "@mui/material/Paper";
interface IProps { interface IProps {
bladeburner: IBladeburner; bladeburner: IBladeburner;
player: IPlayer; player: IPlayer;
action: any; action: IAction;
} }
export function ContractElem(props: IProps): React.ReactElement { export function ContractElem(props: IProps): React.ReactElement {
const setRerender = useState(false)[1]; const setRerender = useState(false)[1];
function rerender(): void {
setRerender((old) => !old);
}
const isActive = const isActive =
props.bladeburner.action.type === ActionTypes["Contract"] && props.action.name === props.bladeburner.action.name; props.bladeburner.action.type === ActionTypes["Contract"] && props.action.name === props.bladeburner.action.name;
const estimatedSuccessChance = props.action.getEstSuccessChance(props.bladeburner);
const computedActionTimeCurrent = Math.min( const computedActionTimeCurrent = Math.min(
props.bladeburner.actionTimeCurrent + props.bladeburner.actionTimeOverflow, props.bladeburner.actionTimeCurrent + props.bladeburner.actionTimeOverflow,
props.bladeburner.actionTimeToComplete, props.bladeburner.actionTimeToComplete,
); );
const maxLevel = props.action.level >= props.action.maxLevel;
const actionTime = props.action.getActionTime(props.bladeburner); const actionTime = props.action.getActionTime(props.bladeburner);
const autolevelCheckboxId = `bladeburner-${props.action.name}-autolevel-checkbox`;
function onStart(): void { const actionData = Contracts[props.action.name];
props.bladeburner.action.type = ActionTypes.Contract; if (actionData === undefined) {
props.bladeburner.action.name = props.action.name; throw new Error(`Cannot find data for ${props.action.name}`);
props.bladeburner.startAction(props.player, props.bladeburner.action);
setRerender((old) => !old);
}
function increaseLevel(): void {
++props.action.level;
if (isActive) props.bladeburner.startAction(props.player, props.bladeburner.action);
setRerender((old) => !old);
}
function decreaseLevel(): void {
--props.action.level;
if (isActive) props.bladeburner.startAction(props.player, props.bladeburner.action);
setRerender((old) => !old);
}
function onAutolevel(event: React.ChangeEvent<HTMLInputElement>): void {
props.action.autoLevel = event.target.checked;
setRerender((old) => !old);
} }
return ( return (
<> <Paper sx={{ my: 1, p: 1 }}>
<h2 style={{ display: "inline-block" }}> {isActive ? (
{isActive ? ( <>
<> <Typography>
<CopyableText value={props.action.name} /> (IN PROGRESS - {formatNumber(computedActionTimeCurrent, 0)} /{" "} <CopyableText value={props.action.name} /> (IN PROGRESS - {formatNumber(computedActionTimeCurrent, 0)} /{" "}
{formatNumber(props.bladeburner.actionTimeToComplete, 0)}) {formatNumber(props.bladeburner.actionTimeToComplete, 0)})
</> </Typography>
) : ( <Typography>
<CopyableText value={props.action.name} /> {createProgressBarText({
)} progress: computedActionTimeCurrent / props.bladeburner.actionTimeToComplete,
</h2> })}
{isActive ? ( </Typography>
<p style={{ display: "block" }}> </>
{createProgressBarText({
progress: computedActionTimeCurrent / props.bladeburner.actionTimeToComplete,
})}
</p>
) : ( ) : (
<> <>
<a onClick={onStart} className="a-link-button" style={{ margin: "3px", padding: "3px" }}> <CopyableText value={props.action.name} />
Start <StartButton
</a> bladeburner={props.bladeburner}
type={ActionTypes.Contract}
name={props.action.name}
rerender={rerender}
/>
</> </>
)} )}
<br /> <br />
<br /> <br />
<pre className="tooltip" style={{ display: "inline-block" }}> <ActionLevel action={props.action} bladeburner={props.bladeburner} isActive={isActive} rerender={rerender} />
<span className="tooltiptext">
{props.action.getSuccessesNeededForNextLevel(BladeburnerConstants.ContractSuccessesPerLevel)} successes needed
for next level
</span>
Level: {props.action.level} / {props.action.maxLevel}
</pre>
<a
onClick={increaseLevel}
style={{ padding: "2px", margin: "2px" }}
className={`tooltip ${maxLevel ? "a-link-button-inactive" : "a-link-button"}`}
>
{isActive && <span className="tooltiptext">WARNING: changing the level will restart the Operation</span>}
</a>
<a
onClick={decreaseLevel}
style={{ padding: "2px", margin: "2px" }}
className={`tooltip ${props.action.level <= 1 ? "a-link-button-inactive" : "a-link-button"}`}
>
{isActive && <span className="tooltiptext">WARNING: changing the level will restart the Operation</span>}
</a>
<br /> <br />
<br /> <br />
<pre style={{ display: "inline-block" }}> <Typography>
<span dangerouslySetInnerHTML={{ __html: props.action.desc }} /> {actionData.desc}
<br /> <br />
<br /> <br />
Estimated success chance: <SuccessChance chance={estimatedSuccessChance} />{" "} <SuccessChance action={props.action} bladeburner={props.bladeburner} />
{props.action.isStealth ? stealthIcon : <></>}
{props.action.isKill ? killIcon : <></>}
<br /> <br />
Time Required: {convertTimeMsToTimeElapsedString(actionTime * 1000)} Time Required: {convertTimeMsToTimeElapsedString(actionTime * 1000)}
<br /> <br />
@ -117,13 +82,9 @@ export function ContractElem(props: IProps): React.ReactElement {
Successes: {props.action.successes} Successes: {props.action.successes}
<br /> <br />
Failures: {props.action.failures} Failures: {props.action.failures}
</pre> </Typography>
<br /> <br />
<label className="tooltip" style={{ color: "white" }} htmlFor={autolevelCheckboxId}> <Autolevel rerender={rerender} action={props.action} />
Autolevel: </Paper>
<span className="tooltiptext">Automatically increase operation level when possible</span>
</label>
<input type="checkbox" id={autolevelCheckboxId} checked={props.action.autoLevel} onChange={onAutolevel} />
</>
); );
} }

@ -14,9 +14,7 @@ export function ContractList(props: IProps): React.ReactElement {
return ( return (
<> <>
{names.map((name: string) => ( {names.map((name: string) => (
<li key={name} className="bladeburner-action"> <ContractElem key={name} bladeburner={props.bladeburner} action={contracts[name]} player={props.player} />
<ContractElem bladeburner={props.bladeburner} action={contracts[name]} player={props.player} />
</li>
))} ))}
</> </>
); );

@ -2,6 +2,7 @@ import * as React from "react";
import { ContractList } from "./ContractList"; import { ContractList } from "./ContractList";
import { IBladeburner } from "../IBladeburner"; import { IBladeburner } from "../IBladeburner";
import { IPlayer } from "../../PersonObjects/IPlayer"; import { IPlayer } from "../../PersonObjects/IPlayer";
import Typography from "@mui/material/Typography";
interface IProps { interface IProps {
bladeburner: IBladeburner; bladeburner: IBladeburner;
@ -11,14 +12,14 @@ interface IProps {
export function ContractPage(props: IProps): React.ReactElement { export function ContractPage(props: IProps): React.ReactElement {
return ( return (
<> <>
<p style={{ display: "block", margin: "4px", padding: "4px" }}> <Typography>
Complete contracts in order to increase your Bladeburner rank and earn money. Failing a contract will cause you Complete contracts in order to increase your Bladeburner rank and earn money. Failing a contract will cause you
to lose HP, which can lead to hospitalization. to lose HP, which can lead to hospitalization.
<br /> <br />
<br /> <br />
You can unlock higher-level contracts by successfully completing them. Higher-level contracts are more You can unlock higher-level contracts by successfully completing them. Higher-level contracts are more
difficult, but grant more rank, experience, and money. difficult, but grant more rank, experience, and money.
</p> </Typography>
<ContractList bladeburner={props.bladeburner} player={props.player} /> <ContractList bladeburner={props.bladeburner} player={props.player} />
</> </>
); );

@ -1,19 +1,30 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { ActionTypes } from "../data/ActionTypes"; import { ActionTypes } from "../data/ActionTypes";
import { createProgressBarText } from "../../../utils/helpers/createProgressBarText"; import { createProgressBarText } from "../../utils/helpers/createProgressBarText";
import { formatNumber, convertTimeMsToTimeElapsedString } from "../../../utils/StringHelperFunctions"; import { formatNumber, convertTimeMsToTimeElapsedString } from "../../utils/StringHelperFunctions";
import { IBladeburner } from "../IBladeburner"; import { IBladeburner } from "../IBladeburner";
import { IAction } from "../IAction";
import { GeneralActions } from "../data/GeneralActions";
import { IPlayer } from "../../PersonObjects/IPlayer"; import { IPlayer } from "../../PersonObjects/IPlayer";
import { CopyableText } from "../../ui/React/CopyableText"; import { CopyableText } from "../../ui/React/CopyableText";
import { StartButton } from "./StartButton";
import Typography from "@mui/material/Typography";
import Box from "@mui/material/Box";
import Paper from "@mui/material/Paper";
interface IProps { interface IProps {
bladeburner: IBladeburner; bladeburner: IBladeburner;
player: IPlayer; player: IPlayer;
action: any; action: IAction;
} }
export function GeneralActionElem(props: IProps): React.ReactElement { export function GeneralActionElem(props: IProps): React.ReactElement {
const setRerender = useState(false)[1]; const setRerender = useState(false)[1];
function rerender(): void {
setRerender((old) => !old);
}
const isActive = props.action.name === props.bladeburner.action.name; const isActive = props.action.name === props.bladeburner.action.name;
const computedActionTimeCurrent = Math.min( const computedActionTimeCurrent = Math.min(
props.bladeburner.actionTimeCurrent + props.bladeburner.actionTimeOverflow, props.bladeburner.actionTimeCurrent + props.bladeburner.actionTimeOverflow,
@ -37,44 +48,44 @@ export function GeneralActionElem(props: IProps): React.ReactElement {
? Math.max(0, Math.min(props.bladeburner.getRecruitmentSuccessChance(props.player), 1)) ? Math.max(0, Math.min(props.bladeburner.getRecruitmentSuccessChance(props.player), 1))
: -1; : -1;
function onStart(): void { const actionData = GeneralActions[props.action.name];
props.bladeburner.action.type = ActionTypes[props.action.name as string]; if (actionData === undefined) {
props.bladeburner.action.name = props.action.name; throw new Error(`Cannot find data for ${props.action.name}`);
props.bladeburner.startAction(props.player, props.bladeburner.action);
setRerender((old) => !old);
} }
return ( return (
<> <Paper sx={{ my: 1, p: 1 }}>
<h2 style={{ display: "inline-block" }}> {isActive ? (
{isActive ? ( <>
<> <Typography>
<CopyableText value={props.action.name} /> (IN PROGRESS - {formatNumber(computedActionTimeCurrent, 0)} /{" "} <CopyableText value={props.action.name} /> (IN PROGRESS - {formatNumber(computedActionTimeCurrent, 0)} /{" "}
{formatNumber(props.bladeburner.actionTimeToComplete, 0)}) {formatNumber(props.bladeburner.actionTimeToComplete, 0)})
</> </Typography>
) : ( <Typography>
<CopyableText value={props.action.name} /> {createProgressBarText({
)} progress: computedActionTimeCurrent / props.bladeburner.actionTimeToComplete,
</h2> })}
{isActive ? ( </Typography>
<p style={{ display: "block" }}>
{createProgressBarText({
progress: computedActionTimeCurrent / props.bladeburner.actionTimeToComplete,
})}
</p>
) : (
<>
<a onClick={onStart} className="a-link-button" style={{ margin: "3px", padding: "3px" }}>
Start
</a>
</> </>
) : (
<Box display="flex" flexDirection="row" alignItems="center">
<Typography>
<CopyableText value={props.action.name} />
</Typography>
<StartButton
bladeburner={props.bladeburner}
type={ActionTypes[props.action.name as string]}
name={props.action.name}
rerender={rerender}
/>
</Box>
)} )}
<br /> <br />
<br /> <br />
<pre style={{ display: "inline-block" }} dangerouslySetInnerHTML={{ __html: props.action.desc }}></pre> <Typography>{actionData.desc}</Typography>
<br /> <br />
<br /> <br />
<pre style={{ display: "inline-block" }}> <Typography>
Time Required: {convertTimeMsToTimeElapsedString(actionTime * 1000)} Time Required: {convertTimeMsToTimeElapsedString(actionTime * 1000)}
{successChance !== -1 && ( {successChance !== -1 && (
<> <>
@ -82,7 +93,7 @@ export function GeneralActionElem(props: IProps): React.ReactElement {
Estimated success chance: {formatNumber(successChance * 100, 1)}% Estimated success chance: {formatNumber(successChance * 100, 1)}%
</> </>
)} )}
</pre> </Typography>
</> </Paper>
); );
} }

@ -20,9 +20,7 @@ export function GeneralActionList(props: IProps): React.ReactElement {
return ( return (
<> <>
{actions.map((action: Action) => ( {actions.map((action: Action) => (
<li key={action.name} className="bladeburner-action"> <GeneralActionElem key={action.name} bladeburner={props.bladeburner} action={action} player={props.player} />
<GeneralActionElem bladeburner={props.bladeburner} action={action} player={props.player} />
</li>
))} ))}
</> </>
); );

@ -2,6 +2,7 @@ import * as React from "react";
import { GeneralActionList } from "./GeneralActionList"; import { GeneralActionList } from "./GeneralActionList";
import { IBladeburner } from "../IBladeburner"; import { IBladeburner } from "../IBladeburner";
import { IPlayer } from "../../PersonObjects/IPlayer"; import { IPlayer } from "../../PersonObjects/IPlayer";
import Typography from "@mui/material/Typography";
interface IProps { interface IProps {
bladeburner: IBladeburner; bladeburner: IBladeburner;
@ -11,10 +12,7 @@ interface IProps {
export function GeneralActionPage(props: IProps): React.ReactElement { export function GeneralActionPage(props: IProps): React.ReactElement {
return ( return (
<> <>
<p style={{ display: "block", margin: "4px", padding: "4px" }}> <Typography>These are generic actions that will assist you in your Bladeburner duties.</Typography>
These are generic actions that will assist you in your Bladeburner duties. They will not affect your Bladeburner
rank in any way.
</p>
<GeneralActionList bladeburner={props.bladeburner} player={props.player} /> <GeneralActionList bladeburner={props.bladeburner} player={props.player} />
</> </>
); );

@ -0,0 +1,13 @@
import React from "react";
import { killIcon } from "../data/Icons";
import Typography from "@mui/material/Typography";
import Tooltip from "@mui/material/Tooltip";
export function KillIcon(): React.ReactElement {
return (
<Tooltip disableInteractive title={<Typography>This action involves retirement</Typography>}>
{killIcon}
</Tooltip>
);
}

@ -1,128 +1,82 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { ActionTypes } from "../data/ActionTypes"; import { ActionTypes } from "../data/ActionTypes";
import { createProgressBarText } from "../../../utils/helpers/createProgressBarText"; import { createProgressBarText } from "../../utils/helpers/createProgressBarText";
import { formatNumber, convertTimeMsToTimeElapsedString } from "../../../utils/StringHelperFunctions"; import { formatNumber, convertTimeMsToTimeElapsedString } from "../../utils/StringHelperFunctions";
import { stealthIcon, killIcon } from "../data/Icons";
import { BladeburnerConstants } from "../data/Constants";
import { createPopup } from "../../ui/React/createPopup";
import { TeamSizePopup } from "./TeamSizePopup";
import { IBladeburner } from "../IBladeburner";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { SuccessChance } from "./SuccessChance"; import { SuccessChance } from "./SuccessChance";
import { ActionLevel } from "./ActionLevel";
import { Autolevel } from "./Autolevel";
import { StartButton } from "./StartButton";
import { TeamSizeButton } from "./TeamSizeButton";
import { IBladeburner } from "../IBladeburner";
import { Operation } from "../Operation";
import { Operations } from "../data/Operations";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { CopyableText } from "../../ui/React/CopyableText"; import { CopyableText } from "../../ui/React/CopyableText";
import Typography from "@mui/material/Typography";
import Paper from "@mui/material/Paper";
interface IProps { interface IProps {
bladeburner: IBladeburner; bladeburner: IBladeburner;
player: IPlayer; player: IPlayer;
action: any; action: Operation;
} }
export function OperationElem(props: IProps): React.ReactElement { export function OperationElem(props: IProps): React.ReactElement {
const setRerender = useState(false)[1]; const setRerender = useState(false)[1];
function rerender(): void {
setRerender((old) => !old);
}
const isActive = const isActive =
props.bladeburner.action.type === ActionTypes["Operation"] && props.action.name === props.bladeburner.action.name; props.bladeburner.action.type === ActionTypes["Operation"] && props.action.name === props.bladeburner.action.name;
const estimatedSuccessChance = props.action.getEstSuccessChance(props.bladeburner);
const computedActionTimeCurrent = Math.min( const computedActionTimeCurrent = Math.min(
props.bladeburner.actionTimeCurrent + props.bladeburner.actionTimeOverflow, props.bladeburner.actionTimeCurrent + props.bladeburner.actionTimeOverflow,
props.bladeburner.actionTimeToComplete, props.bladeburner.actionTimeToComplete,
); );
const maxLevel = props.action.level >= props.action.maxLevel;
const actionTime = props.action.getActionTime(props.bladeburner); const actionTime = props.action.getActionTime(props.bladeburner);
const autolevelCheckboxId = `bladeburner-${props.action.name}-autolevel-checkbox`;
function onStart(): void { const actionData = Operations[props.action.name];
props.bladeburner.action.type = ActionTypes.Operation; if (actionData === undefined) {
props.bladeburner.action.name = props.action.name; throw new Error(`Cannot find data for ${props.action.name}`);
props.bladeburner.startAction(props.player, props.bladeburner.action);
setRerender((old) => !old);
}
function onTeam(): void {
const popupId = "bladeburner-operation-set-team-size-popup";
createPopup(popupId, TeamSizePopup, {
bladeburner: props.bladeburner,
action: props.action,
popupId: popupId,
});
}
function increaseLevel(): void {
++props.action.level;
if (isActive) props.bladeburner.startAction(props.player, props.bladeburner.action);
setRerender((old) => !old);
}
function decreaseLevel(): void {
--props.action.level;
if (isActive) props.bladeburner.startAction(props.player, props.bladeburner.action);
setRerender((old) => !old);
}
function onAutolevel(event: React.ChangeEvent<HTMLInputElement>): void {
props.action.autoLevel = event.target.checked;
setRerender((old) => !old);
} }
return ( return (
<> <Paper sx={{ my: 1, p: 1 }}>
<h2 style={{ display: "inline-block" }}> {isActive ? (
{isActive ? ( <>
<> <Typography>
<CopyableText value={props.action.name} /> (IN PROGRESS - {formatNumber(computedActionTimeCurrent, 0)} /{" "} <CopyableText value={props.action.name} /> (IN PROGRESS - {formatNumber(computedActionTimeCurrent, 0)} /{" "}
{formatNumber(props.bladeburner.actionTimeToComplete, 0)}) {formatNumber(props.bladeburner.actionTimeToComplete, 0)})
</> </Typography>
) : ( <Typography>
<CopyableText value={props.action.name} /> {createProgressBarText({
)} progress: computedActionTimeCurrent / props.bladeburner.actionTimeToComplete,
</h2> })}
{isActive ? ( </Typography>
<p style={{ display: "block" }}> </>
{createProgressBarText({
progress: computedActionTimeCurrent / props.bladeburner.actionTimeToComplete,
})}
</p>
) : ( ) : (
<> <>
<a onClick={onStart} className="a-link-button" style={{ margin: "3px", padding: "3px" }}> <CopyableText value={props.action.name} />
Start <StartButton
</a> bladeburner={props.bladeburner}
<a onClick={onTeam} style={{ margin: "3px", padding: "3px" }} className="a-link-button"> type={ActionTypes.Operation}
Set Team Size (Curr Size: {formatNumber(props.action.teamCount, 0)}) name={props.action.name}
</a> rerender={rerender}
/>
<TeamSizeButton action={props.action} bladeburner={props.bladeburner} />
</> </>
)} )}
<br /> <br />
<br /> <br />
<pre className="tooltip" style={{ display: "inline-block" }}>
<span className="tooltiptext"> <ActionLevel action={props.action} bladeburner={props.bladeburner} isActive={isActive} rerender={rerender} />
{props.action.getSuccessesNeededForNextLevel(BladeburnerConstants.OperationSuccessesPerLevel)} successes
needed for next level
</span>
Level: {props.action.level} / {props.action.maxLevel}
</pre>
<a
onClick={increaseLevel}
style={{ padding: "2px", margin: "2px" }}
className={`tooltip ${maxLevel ? "a-link-button-inactive" : "a-link-button"}`}
>
{isActive && <span className="tooltiptext">WARNING: changing the level will restart the Operation</span>}
</a>
<a
onClick={decreaseLevel}
style={{ padding: "2px", margin: "2px" }}
className={`tooltip ${props.action.level <= 1 ? "a-link-button-inactive" : "a-link-button"}`}
>
{isActive && <span className="tooltiptext">WARNING: changing the level will restart the Operation</span>}
</a>
<br /> <br />
<br /> <br />
<pre style={{ display: "inline-block" }}> <Typography>
<span dangerouslySetInnerHTML={{ __html: props.action.desc }} /> {actionData.desc}
<br /> <br />
<br /> <br />
Estimated success chance: <SuccessChance chance={estimatedSuccessChance} />{" "} <SuccessChance action={props.action} bladeburner={props.bladeburner} />
{props.action.isStealth ? stealthIcon : <></>}
{props.action.isKill ? killIcon : <></>}
<br /> <br />
Time Required: {convertTimeMsToTimeElapsedString(actionTime * 1000)} Time Required: {convertTimeMsToTimeElapsedString(actionTime * 1000)}
<br /> <br />
@ -131,13 +85,9 @@ export function OperationElem(props: IProps): React.ReactElement {
Successes: {props.action.successes} Successes: {props.action.successes}
<br /> <br />
Failures: {props.action.failures} Failures: {props.action.failures}
</pre> </Typography>
<br /> <br />
<label className="tooltip" style={{ color: "white" }} htmlFor={autolevelCheckboxId}> <Autolevel rerender={rerender} action={props.action} />
Autolevel: </Paper>
<span className="tooltiptext">Automatically increase operation level when possible</span>
</label>
<input type="checkbox" id={autolevelCheckboxId} checked={props.action.autoLevel} onChange={onAutolevel} />
</>
); );
} }

@ -14,9 +14,7 @@ export function OperationList(props: IProps): React.ReactElement {
return ( return (
<> <>
{names.map((name: string) => ( {names.map((name: string) => (
<li key={name} className="bladeburner-action"> <OperationElem key={name} bladeburner={props.bladeburner} action={operations[name]} player={props.player} />
<OperationElem bladeburner={props.bladeburner} action={operations[name]} player={props.player} />
</li>
))} ))}
</> </>
); );

@ -2,6 +2,7 @@ import * as React from "react";
import { OperationList } from "./OperationList"; import { OperationList } from "./OperationList";
import { IBladeburner } from "../IBladeburner"; import { IBladeburner } from "../IBladeburner";
import { IPlayer } from "../../PersonObjects/IPlayer"; import { IPlayer } from "../../PersonObjects/IPlayer";
import Typography from "@mui/material/Typography";
interface IProps { interface IProps {
bladeburner: IBladeburner; bladeburner: IBladeburner;
@ -11,7 +12,7 @@ interface IProps {
export function OperationPage(props: IProps): React.ReactElement { export function OperationPage(props: IProps): React.ReactElement {
return ( return (
<> <>
<p style={{ display: "block", margin: "4px", padding: "4px" }}> <Typography>
Carry out operations for the Bladeburner division. Failing an operation will reduce your Bladeburner rank. It Carry out operations for the Bladeburner division. Failing an operation will reduce your Bladeburner rank. It
will also cause you to lose HP, which can lead to hospitalization. In general, operations are harder and more will also cause you to lose HP, which can lead to hospitalization. In general, operations are harder and more
punishing than contracts, but are also more rewarding. punishing than contracts, but are also more rewarding.
@ -27,7 +28,7 @@ export function OperationPage(props: IProps): React.ReactElement {
<br /> <br />
You can unlock higher-level operations by successfully completing them. Higher-level operations are more You can unlock higher-level operations by successfully completing them. Higher-level operations are more
difficult, but grant more rank and experience. difficult, but grant more rank and experience.
</p> </Typography>
<OperationList bladeburner={props.bladeburner} player={props.player} /> <OperationList bladeburner={props.bladeburner} player={props.player} />
</> </>
); );

@ -1,8 +1,15 @@
import React from "react"; import React from "react";
import { CopyableText } from "../../ui/React/CopyableText"; import { CopyableText } from "../../ui/React/CopyableText";
import { formatNumber } from "../../../utils/StringHelperFunctions"; import { formatNumber } from "../../utils/StringHelperFunctions";
import { IBladeburner } from "../IBladeburner"; import { IBladeburner } from "../IBladeburner";
import Typography from "@mui/material/Typography";
import IconButton from "@mui/material/IconButton";
import Box from "@mui/material/Box";
import Paper from "@mui/material/Paper";
import AddIcon from "@mui/icons-material/Add";
import CloseIcon from "@mui/icons-material/Close";
interface IProps { interface IProps {
skill: any; skill: any;
bladeburner: IBladeburner; bladeburner: IBladeburner;
@ -28,26 +35,30 @@ export function SkillElem(props: IProps): React.ReactElement {
} }
return ( return (
<> <Paper sx={{ my: 1, p: 1 }}>
<h2 style={{ display: "inline-block" }}> <Box display="flex" flexDirection="row" alignItems="center">
<CopyableText value={props.skill.name} /> <Typography>
</h2> <CopyableText value={props.skill.name} />
<a </Typography>
onClick={onClick} {!canLevel || maxLvl ? (
style={{ display: "inline-block", margin: "3px", padding: "3px" }} <IconButton disabled>
className={canLevel && !maxLvl ? "a-link-button" : "a-link-button-inactive"} <CloseIcon />
> </IconButton>
Level ) : (
</a> <IconButton onClick={onClick}>
<AddIcon />
</IconButton>
)}
</Box>
<br /> <br />
<br /> <br />
<p style={{ display: "block" }}>Level: {currentLevel}</p> <Typography>Level: {currentLevel}</Typography>
{maxLvl ? ( {maxLvl ? (
<p style={{ color: "red", display: "block" }}>MAX LEVEL</p> <Typography>MAX LEVEL</Typography>
) : ( ) : (
<p style={{ display: "block" }}>Skill Points required: {formatNumber(pointCost, 0)}</p> <Typography>Skill Points required: {formatNumber(pointCost, 0)}</Typography>
)} )}
<p style={{ display: "inline-block" }} dangerouslySetInnerHTML={{ __html: props.skill.desc }} /> <Typography>{props.skill.desc}</Typography>
</> </Paper>
); );
} }

@ -12,9 +12,7 @@ export function SkillList(props: IProps): React.ReactElement {
return ( return (
<> <>
{Object.keys(Skills).map((skill: string) => ( {Object.keys(Skills).map((skill: string) => (
<li key={skill} className="bladeburner-action"> <SkillElem key={skill} bladeburner={props.bladeburner} skill={Skills[skill]} onUpgrade={props.onUpgrade} />
<SkillElem bladeburner={props.bladeburner} skill={Skills[skill]} onUpgrade={props.onUpgrade} />
</li>
))} ))}
</> </>
); );

@ -1,9 +1,10 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { SkillList } from "./SkillList"; import { SkillList } from "./SkillList";
import { BladeburnerConstants } from "../data/Constants"; import { BladeburnerConstants } from "../data/Constants";
import { formatNumber } from "../../../utils/StringHelperFunctions"; import { formatNumber } from "../../utils/StringHelperFunctions";
import { IBladeburner } from "../IBladeburner"; import { IBladeburner } from "../IBladeburner";
import Typography from "@mui/material/Typography";
import { BitNodeMultipliers } from "../../BitNode/BitNodeMultipliers";
interface IProps { interface IProps {
bladeburner: IBladeburner; bladeburner: IBladeburner;
} }
@ -18,45 +19,48 @@ export function SkillPage(props: IProps): React.ReactElement {
return ( return (
<> <>
<p> <Typography>
<strong>Skill Points: {formatNumber(props.bladeburner.skillPoints, 0)}</strong> <strong>Skill Points: {formatNumber(props.bladeburner.skillPoints, 0)}</strong>
</p> </Typography>
<p> <Typography>
You will gain one skill point every {BladeburnerConstants.RanksPerSkillPoint} ranks. You will gain one skill point every{" "}
{BladeburnerConstants.RanksPerSkillPoint * BitNodeMultipliers.BladeburnerSkillCost} ranks.
<br /> <br />
<br /> <br />
Note that when upgrading a skill, the benefit for that skill is additive. However, the effects of different Note that when upgrading a skill, the benefit for that skill is additive. However, the effects of different
skills with each other is multiplicative. skills with each other is multiplicative.
<br /> <br />
</p> </Typography>
<br /> <br />
{valid(mults["successChanceAll"]) && <p>Total Success Chance: x{formatNumber(mults["successChanceAll"], 3)}</p>} {valid(mults["successChanceAll"]) && (
<Typography>Total Success Chance: x{formatNumber(mults["successChanceAll"], 3)}</Typography>
)}
{valid(mults["successChanceStealth"]) && ( {valid(mults["successChanceStealth"]) && (
<p>Stealth Success Chance: x{formatNumber(mults["successChanceStealth"], 3)}</p> <Typography>Stealth Success Chance: x{formatNumber(mults["successChanceStealth"], 3)}</Typography>
)} )}
{valid(mults["successChanceKill"]) && ( {valid(mults["successChanceKill"]) && (
<p>Retirement Success Chance: x{formatNumber(mults["successChanceKill"], 3)}</p> <Typography>Retirement Success Chance: x{formatNumber(mults["successChanceKill"], 3)}</Typography>
)} )}
{valid(mults["successChanceContract"]) && ( {valid(mults["successChanceContract"]) && (
<p>Contract Success Chance: x{formatNumber(mults["successChanceContract"], 3)}</p> <Typography>Contract Success Chance: x{formatNumber(mults["successChanceContract"], 3)}</Typography>
)} )}
{valid(mults["successChanceOperation"]) && ( {valid(mults["successChanceOperation"]) && (
<p>Operation Success Chance: x{formatNumber(mults["successChanceOperation"], 3)}</p> <Typography>Operation Success Chance: x{formatNumber(mults["successChanceOperation"], 3)}</Typography>
)} )}
{valid(mults["successChanceEstimate"]) && ( {valid(mults["successChanceEstimate"]) && (
<p>Synthoid Data Estimate: x{formatNumber(mults["successChanceEstimate"], 3)}</p> <Typography>Synthoid Data Estimate: x{formatNumber(mults["successChanceEstimate"], 3)}</Typography>
)} )}
{valid(mults["actionTime"]) && <p>Action Time: x{formatNumber(mults["actionTime"], 3)}</p>} {valid(mults["actionTime"]) && <Typography>Action Time: x{formatNumber(mults["actionTime"], 3)}</Typography>}
{valid(mults["effHack"]) && <p>Hacking Skill: x{formatNumber(mults["effHack"], 3)}</p>} {valid(mults["effHack"]) && <Typography>Hacking Skill: x{formatNumber(mults["effHack"], 3)}</Typography>}
{valid(mults["effStr"]) && <p>Strength: x{formatNumber(mults["effStr"], 3)}</p>} {valid(mults["effStr"]) && <Typography>Strength: x{formatNumber(mults["effStr"], 3)}</Typography>}
{valid(mults["effDef"]) && <p>Defense: x{formatNumber(mults["effDef"], 3)}</p>} {valid(mults["effDef"]) && <Typography>Defense: x{formatNumber(mults["effDef"], 3)}</Typography>}
{valid(mults["effDex"]) && <p>Dexterity: x{formatNumber(mults["effDex"], 3)}</p>} {valid(mults["effDex"]) && <Typography>Dexterity: x{formatNumber(mults["effDex"], 3)}</Typography>}
{valid(mults["effAgi"]) && <p>Agility: x{formatNumber(mults["effAgi"], 3)}</p>} {valid(mults["effAgi"]) && <Typography>Agility: x{formatNumber(mults["effAgi"], 3)}</Typography>}
{valid(mults["effCha"]) && <p>Charisma: x{formatNumber(mults["effCha"], 3)}</p>} {valid(mults["effCha"]) && <Typography>Charisma: x{formatNumber(mults["effCha"], 3)}</Typography>}
{valid(mults["effInt"]) && <p>Intelligence: x{formatNumber(mults["effInt"], 3)}</p>} {valid(mults["effInt"]) && <Typography>Intelligence: x{formatNumber(mults["effInt"], 3)}</Typography>}
{valid(mults["stamina"]) && <p>Stamina: x{formatNumber(mults["stamina"], 3)}</p>} {valid(mults["stamina"]) && <Typography>Stamina: x{formatNumber(mults["stamina"], 3)}</Typography>}
{valid(mults["money"]) && <p>Contract Money: x{formatNumber(mults["money"], 3)}</p>} {valid(mults["money"]) && <Typography>Contract Money: x{formatNumber(mults["money"], 3)}</Typography>}
{valid(mults["expGain"]) && <p>Exp Gain: x{formatNumber(mults["expGain"], 3)}</p>} {valid(mults["expGain"]) && <Typography>Exp Gain: x{formatNumber(mults["expGain"], 3)}</Typography>}
<br /> <br />
<SkillList bladeburner={props.bladeburner} onUpgrade={() => setRerender((old) => !old)} /> <SkillList bladeburner={props.bladeburner} onUpgrade={() => setRerender((old) => !old)} />
</> </>

@ -0,0 +1,44 @@
import React from "react";
import { IBladeburner } from "../IBladeburner";
import { BlackOperation } from "../BlackOperation";
import { use } from "../../ui/Context";
import Button from "@mui/material/Button";
interface IProps {
bladeburner: IBladeburner;
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 });
if (action == null) {
throw new Error("Failed to get Operation Object for: " + props.name);
}
let disabled = false;
if (action.count < 1) {
disabled = true;
}
if (props.name === "Raid" && props.bladeburner.getCurrentCity().comms === 0) {
disabled = true;
}
if (action instanceof BlackOperation && props.bladeburner.rank < action.reqdRank) {
disabled = true;
}
function onStart(): void {
if (disabled) return;
props.bladeburner.action.type = props.type;
props.bladeburner.action.name = props.name;
props.bladeburner.startAction(player, props.bladeburner.action);
props.rerender();
}
return (
<Button sx={{ mx: 1 }} disabled={disabled} onClick={onStart}>
Start
</Button>
);
}

@ -1,18 +1,21 @@
import React, { useState, useEffect } from "react"; import React, { useState, useEffect } from "react";
import { formatNumber, convertTimeMsToTimeElapsedString } from "../../../utils/StringHelperFunctions"; import { formatNumber, convertTimeMsToTimeElapsedString } from "../../utils/StringHelperFunctions";
import { BladeburnerConstants } from "../data/Constants"; import { BladeburnerConstants } from "../data/Constants";
import { IPlayer } from "../../PersonObjects/IPlayer"; import { IPlayer } from "../../PersonObjects/IPlayer";
import { Money } from "../../ui/React/Money"; import { Money } from "../../ui/React/Money";
import { StatsTable } from "../../ui/React/StatsTable"; import { StatsTable } from "../../ui/React/StatsTable";
import { numeralWrapper } from "../../ui/numeralFormat"; import { numeralWrapper } from "../../ui/numeralFormat";
import { dialogBoxCreate } from "../../../utils/DialogBox";
import { createPopup } from "../../ui/React/createPopup";
import { Factions } from "../../Faction/Factions"; import { Factions } from "../../Faction/Factions";
import { IRouter } from "../../ui/Router"; import { IRouter } from "../../ui/Router";
import { joinFaction } from "../../Faction/FactionHelpers"; import { joinFaction } from "../../Faction/FactionHelpers";
import { IBladeburner } from "../IBladeburner"; import { IBladeburner } from "../IBladeburner";
import { TravelPopup } from "./TravelPopup"; import { TravelModal } from "./TravelModal";
import Typography from "@mui/material/Typography";
import Tooltip from "@mui/material/Tooltip";
import Box from "@mui/material/Box";
import Button from "@mui/material/Button";
import Paper from "@mui/material/Paper";
interface IProps { interface IProps {
bladeburner: IBladeburner; bladeburner: IBladeburner;
@ -21,131 +24,146 @@ interface IProps {
} }
export function Stats(props: IProps): React.ReactElement { export function Stats(props: IProps): React.ReactElement {
const [travelOpen, setTravelOpen] = useState(false);
const setRerender = useState(false)[1]; const setRerender = useState(false)[1];
const inFaction = props.bladeburner.rank >= BladeburnerConstants.RankNeededForFaction;
useEffect(() => { useEffect(() => {
const id = setInterval(() => setRerender((old) => !old), 1000); const id = setInterval(() => setRerender((old) => !old), 1000);
return () => clearInterval(id); return () => clearInterval(id);
}, []); }, []);
function openStaminaHelp(): void {
dialogBoxCreate(
"Performing actions will use up your stamina.<br><br>" +
"Your max stamina is determined primarily by your agility stat.<br><br>" +
"Your stamina gain rate is determined by both your agility and your " +
"max stamina. Higher max stamina leads to a higher gain rate.<br><br>" +
"Once your " +
"stamina falls below 50% of its max value, it begins to negatively " +
"affect the success rate of your contracts/operations. This penalty " +
"is shown in the overview panel. If the penalty is 15%, then this means " +
"your success rate would be multipled by 85% (100 - 15).<br><br>" +
"Your max stamina and stamina gain rate can also be increased by " +
"training, or through skills and Augmentation upgrades.",
);
}
function openPopulationHelp(): void {
dialogBoxCreate(
"The success rate of your contracts/operations depends on " +
"the population of Synthoids in your current city. " +
"The success rate that is shown to you is only an estimate, " +
"and it is based on your Synthoid population estimate.<br><br>" +
"Therefore, it is important that this Synthoid population estimate " +
"is accurate so that you have a better idea of your " +
"success rate for contracts/operations. Certain " +
"actions will increase the accuracy of your population " +
"estimate.<br><br>" +
"The Synthoid populations of cities can change due to your " +
"actions or random events. If random events occur, they will " +
"be logged in the Bladeburner Console.",
);
}
function openTravel(): void {
const popupId = "bladeburner-travel-popup";
createPopup(popupId, TravelPopup, {
bladeburner: props.bladeburner,
popupId: popupId,
});
}
function openFaction(): void { function openFaction(): void {
if (!inFaction) return;
const faction = Factions["Bladeburners"]; const faction = Factions["Bladeburners"];
if (faction.isMember) { if (!faction.isMember) {
props.router.toFaction(faction); joinFaction(faction);
} else {
if (props.bladeburner.rank >= BladeburnerConstants.RankNeededForFaction) {
joinFaction(faction);
dialogBoxCreate("Congratulations! You were accepted into the Bladeburners faction");
} else {
dialogBoxCreate("You need a rank of 25 to join the Bladeburners Faction!");
}
} }
props.router.toFaction(faction);
} }
return ( return (
<> <Paper sx={{ p: 1 }}>
<p className="tooltip" style={{ display: "inline-block" }}> <Box display="flex">
Rank: {formatNumber(props.bladeburner.rank, 2)} <Tooltip disableInteractive title={<Typography>Your rank within the Bladeburner division.</Typography>}>
<span className="tooltiptext">Your rank within the Bladeburner division.</span> <Typography>Rank: {formatNumber(props.bladeburner.rank, 2)}</Typography>
</p> </Tooltip>
</Box>
<br /> <br />
<p> <Box display="flex">
Stamina: {formatNumber(props.bladeburner.stamina, 3)} / {formatNumber(props.bladeburner.maxStamina, 3)} <Tooltip
</p> disableInteractive
<div className="help-tip" onClick={openStaminaHelp}> title={
? <Typography>
</div> Performing actions will use up your stamina.
<br />
<br />
Your max stamina is determined primarily by your agility stat.
<br />
<br />
Your stamina gain rate is determined by both your agility and your max stamina. Higher max stamina leads
to a higher gain rate.
<br />
<br />
Once your stamina falls below 50% of its max value, it begins to negatively affect the success rate of
your contracts/operations. This penalty is shown in the overview panel. If the penalty is 15%, then this
means your success rate would be multipled by 85% (100 - 15).
<br />
<br />
Your max stamina and stamina gain rate can also be increased by training, or through skills and
Augmentation upgrades.
</Typography>
}
>
<Typography>
Stamina: {formatNumber(props.bladeburner.stamina, 3)} / {formatNumber(props.bladeburner.maxStamina, 3)}
</Typography>
</Tooltip>
</Box>
<br /> <br />
<p>Stamina Penalty: {formatNumber((1 - props.bladeburner.calculateStaminaPenalty()) * 100, 1)}%</p> <Typography>
Stamina Penalty: {formatNumber((1 - props.bladeburner.calculateStaminaPenalty()) * 100, 1)}%
</Typography>
<br /> <br />
<p>Team Size: {formatNumber(props.bladeburner.teamSize, 0)}</p> <Typography>Team Size: {formatNumber(props.bladeburner.teamSize, 0)}</Typography>
<p>Team Members Lost: {formatNumber(props.bladeburner.teamLost, 0)}</p> <Typography>Team Members Lost: {formatNumber(props.bladeburner.teamLost, 0)}</Typography>
<br /> <br />
<p>Num Times Hospitalized: {props.bladeburner.numHosp}</p> <Typography>Num Times Hospitalized: {props.bladeburner.numHosp}</Typography>
<p> <Typography>
Money Lost From Hospitalizations: <Money money={props.bladeburner.moneyLost} /> Money Lost From Hospitalizations: <Money money={props.bladeburner.moneyLost} />
</p> </Typography>
<br /> <br />
<p>Current City: {props.bladeburner.city}</p> <Typography>Current City: {props.bladeburner.city}</Typography>
<p className="tooltip" style={{ display: "inline-block" }}> <Box display="flex">
Est. Synthoid Population: {numeralWrapper.formatPopulation(props.bladeburner.getCurrentCity().popEst)} <Tooltip
<span className="tooltiptext"> disableInteractive
This is your Bladeburner division's estimate of how many Synthoids exist in your current city. title={
</span> <Typography>
</p> This is your Bladeburner division's estimate of how many Synthoids exist in your current city. An accurate
<div className="help-tip" onClick={openPopulationHelp}> population count increases success rate estimates.
? </Typography>
</div> }
>
<Typography>
Est. Synthoid Population: {numeralWrapper.formatPopulation(props.bladeburner.getCurrentCity().popEst)}
</Typography>
</Tooltip>
</Box>
<br /> <br />
<p className="tooltip" style={{ display: "inline-block" }}> <Box display="flex">
Est. Synthoid Communities: {formatNumber(props.bladeburner.getCurrentCity().comms, 0)} <Tooltip
<span className="tooltiptext"> disableInteractive
This is your Bladeburner divison's estimate of how many Synthoid communities exist in your current city. title={
</span> <Typography>
</p> This is your Bladeburner divison's estimate of how many Synthoid communities exist in your current city.
</Typography>
}
>
<Typography>
Est. Synthoid Communities: {formatNumber(props.bladeburner.getCurrentCity().comms, 0)}
</Typography>
</Tooltip>
</Box>
<br /> <br />
<p className="tooltip" style={{ display: "inline-block" }}> <Box display="flex">
City Chaos: {formatNumber(props.bladeburner.getCurrentCity().chaos)} <Tooltip
<span className="tooltiptext"> disableInteractive
The city's chaos level due to tensions and conflicts between humans and Synthoids. Having too high of a chaos title={
level can make contracts and operations harder. <Typography>
</span> The city's chaos level due to tensions and conflicts between humans and Synthoids. Having too high of a
</p> chaos level can make contracts and operations harder.
</Typography>
}
>
<Typography>City Chaos: {formatNumber(props.bladeburner.getCurrentCity().chaos)}</Typography>
</Tooltip>
</Box>
<br /> <br />
<br /> {(props.bladeburner.storedCycles / BladeburnerConstants.CyclesPerSecond) * 1000 > 15000 && (
<p className="tooltip" style={{ display: "inline-block" }}> <>
Bonus time:{" "} <Box display="flex">
{convertTimeMsToTimeElapsedString( <Tooltip
(props.bladeburner.storedCycles / BladeburnerConstants.CyclesPerSecond) * 1000, disableInteractive
)} title={
<br /> <Typography>
<span className="tooltiptext"> You gain bonus time while offline or when the game is inactive (e.g. when the tab is throttled by
You gain bonus time while offline or when the game is inactive (e.g. when the tab is throttled by browser). browser). Bonus time makes the Bladeburner mechanic progress faster, up to 5x the normal speed.
Bonus time makes the Bladeburner mechanic progress faster, up to 5x the normal speed. </Typography>
</span> }
</p> >
<p>Skill Points: {formatNumber(props.bladeburner.skillPoints, 0)}</p> <Typography>
Bonus time:{" "}
{convertTimeMsToTimeElapsedString(
(props.bladeburner.storedCycles / BladeburnerConstants.CyclesPerSecond) * 1000,
)}
</Typography>
</Tooltip>
</Box>
<br />
</>
)}
<Typography>Skill Points: {formatNumber(props.bladeburner.skillPoints, 0)}</Typography>
<br /> <br />
<StatsTable <StatsTable
rows={[ rows={[
@ -156,17 +174,15 @@ export function Stats(props: IProps): React.ReactElement {
]} ]}
/> />
<br /> <br />
<a onClick={openTravel} className="a-link-button" style={{ display: "inline-block" }}> <Button onClick={() => setTravelOpen(true)}>Travel</Button>
Travel <Tooltip title={!inFaction ? <Typography>Rank 25 required.</Typography> : ""}>
</a> <span>
<a onClick={openFaction} className="a-link-button tooltip" style={{ display: "inline-block" }}> <Button disabled={!inFaction} onClick={openFaction}>
<span className="tooltiptext"> Faction
Apply to the Bladeburner Faction, or go to the faction page if you are already a member </Button>
</span> </span>
Faction </Tooltip>
</a> <TravelModal open={travelOpen} onClose={() => setTravelOpen(false)} bladeburner={props.bladeburner} />
<br /> </Paper>
<br />
</>
); );
} }

@ -0,0 +1,13 @@
import React from "react";
import { stealthIcon } from "../data/Icons";
import Typography from "@mui/material/Typography";
import Tooltip from "@mui/material/Tooltip";
export function StealthIcon(): React.ReactElement {
return (
<Tooltip disableInteractive title={<Typography>This action involves stealth</Typography>}>
{stealthIcon}
</Tooltip>
);
}

@ -1,18 +1,33 @@
import React from "react"; import React from "react";
import { formatNumber } from "../../../utils/StringHelperFunctions"; import { formatNumber } from "../../utils/StringHelperFunctions";
import { StealthIcon } from "./StealthIcon";
import { KillIcon } from "./KillIcon";
import { IAction } from "../IAction";
import { IBladeburner } from "../IBladeburner";
interface IProps { interface IProps {
chance: number[]; bladeburner: IBladeburner;
action: IAction;
} }
export function SuccessChance(props: IProps): React.ReactElement { export function SuccessChance(props: IProps): React.ReactElement {
if (props.chance[0] === props.chance[1]) { const estimatedSuccessChance = props.action.getEstSuccessChance(props.bladeburner);
return <>{formatNumber(props.chance[0] * 100, 1)}%</>;
let chance = <></>;
if (estimatedSuccessChance[0] === estimatedSuccessChance[1]) {
chance = <>{formatNumber(estimatedSuccessChance[0] * 100, 1)}%</>;
} else {
chance = (
<>
{formatNumber(estimatedSuccessChance[0] * 100, 1)}% ~ {formatNumber(estimatedSuccessChance[1] * 100, 1)}%
</>
);
} }
return ( return (
<> <>
{formatNumber(props.chance[0] * 100, 1)}% ~ {formatNumber(props.chance[1] * 100, 1)}% Estimated success chance: {chance} {props.action.isStealth ? <StealthIcon /> : <></>}
{props.action.isKill ? <KillIcon /> : <></>}
</> </>
); );
} }

@ -0,0 +1,22 @@
import React, { useState } from "react";
import { Operation } from "../Operation";
import { IBladeburner } from "../IBladeburner";
import { TeamSizeModal } from "./TeamSizeModal";
import { formatNumber } from "../../utils/StringHelperFunctions";
import Button from "@mui/material/Button";
interface IProps {
action: Operation;
bladeburner: IBladeburner;
}
export function TeamSizeButton(props: IProps): React.ReactElement {
const [open, setOpen] = useState(false);
return (
<>
<Button disabled={props.bladeburner.teamSize === 0} onClick={() => setOpen(true)}>
Set Team Size (Curr Size: {formatNumber(props.action.teamCount, 0)})
</Button>
<TeamSizeModal open={open} onClose={() => setOpen(false)} action={props.action} bladeburner={props.bladeburner} />
</>
);
}

@ -1,16 +1,20 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { removePopup } from "../../ui/React/createPopup"; import { dialogBoxCreate } from "../../ui/React/DialogBox";
import { dialogBoxCreate } from "../../../utils/DialogBox"; import { Modal } from "../../ui/React/Modal";
import { Action } from "../Action"; import { Action } from "../Action";
import { IBladeburner } from "../IBladeburner"; import { IBladeburner } from "../IBladeburner";
import Typography from "@mui/material/Typography";
import Button from "@mui/material/Button";
import TextField from "@mui/material/TextField";
interface IProps { interface IProps {
bladeburner: IBladeburner; bladeburner: IBladeburner;
action: Action; action: Action;
popupId: string; open: boolean;
onClose: () => void;
} }
export function TeamSizePopup(props: IProps): React.ReactElement { export function TeamSizeModal(props: IProps): React.ReactElement {
const [teamSize, setTeamSize] = useState<number | undefined>(); const [teamSize, setTeamSize] = useState<number | undefined>();
function confirmTeamSize(): void { function confirmTeamSize(): void {
@ -21,25 +25,32 @@ export function TeamSizePopup(props: IProps): React.ReactElement {
} else { } else {
props.action.teamCount = num; props.action.teamCount = num;
} }
removePopup(props.popupId); props.onClose();
}
function onTeamSize(event: React.ChangeEvent<HTMLInputElement>): void {
const x = parseFloat(event.target.value);
if (x > props.bladeburner.teamSize) setTeamSize(props.bladeburner.teamSize);
else setTeamSize(x);
} }
return ( return (
<> <Modal open={props.open} onClose={props.onClose}>
<p> <Typography>
Enter the amount of team members you would like to take on this Op. If you do not have the specified number of Enter the amount of team members you would like to take on this Op. If you do not have the specified number of
team members, then as many as possible will be used. Note that team members may be lost during operations. team members, then as many as possible will be used. Note that team members may be lost during operations.
</p> </Typography>
<input <TextField
autoFocus autoFocus
variant="standard"
type="number" type="number"
placeholder="Team size" placeholder="Team size"
className="text-input" value={teamSize}
onChange={(event) => setTeamSize(parseFloat(event.target.value))} onChange={onTeamSize}
/> />
<a className="a-link-button" onClick={confirmTeamSize}> <Button sx={{ mx: 2 }} onClick={confirmTeamSize}>
Confirm Confirm
</a> </Button>
</> </Modal>
); );
} }

@ -0,0 +1,41 @@
import React from "react";
import { IBladeburner } from "../IBladeburner";
import { WorldMap } from "../../ui/React/WorldMap";
import { Modal } from "../../ui/React/Modal";
import { CityName } from "../../Locations/data/CityNames";
import { Settings } from "../../Settings/Settings";
import Typography from "@mui/material/Typography";
import Button from "@mui/material/Button";
interface IProps {
bladeburner: IBladeburner;
open: boolean;
onClose: () => void;
}
export function TravelModal(props: IProps): React.ReactElement {
function travel(city: string): void {
props.bladeburner.city = city;
props.onClose();
}
return (
<Modal open={props.open} onClose={props.onClose}>
<>
<Typography>
Travel to a different city for your Bladeburner activities. This does not cost any money. The city you are in
for your Bladeburner duties does not affect your location in the game otherwise.
</Typography>
{Settings.DisableASCIIArt ? (
Object.values(CityName).map((city: CityName) => (
<Button key={city} onClick={() => travel(city)}>
{city}
</Button>
))
) : (
<WorldMap currentCity={props.bladeburner.city as CityName} onTravel={(city: CityName) => travel(city)} />
)}
</>
</Modal>
);
}

@ -1,36 +0,0 @@
import React from "react";
import { removePopup } from "../../ui/React/createPopup";
import { IBladeburner } from "../IBladeburner";
import { WorldMap } from "../../ui/React/WorldMap";
import { CityName } from "../../Locations/data/CityNames";
import { Settings } from "../../Settings/Settings";
interface IProps {
bladeburner: IBladeburner;
popupId: string;
}
export function TravelPopup(props: IProps): React.ReactElement {
function travel(city: string): void {
props.bladeburner.city = city;
removePopup(props.popupId);
}
return (
<>
<p>
Travel to a different city for your Bladeburner activities. This does not cost any money. The city you are in
for your Bladeburner duties does not affect your location in the game otherwise.
</p>
{Settings.DisableASCIIArt ? (
Object.values(CityName).map((city: CityName) => (
<button key={city} className="std-button" onClick={() => travel(city)}>
{city}
</button>
))
) : (
<WorldMap currentCity={props.bladeburner.city as CityName} onTravel={(city: CityName) => travel(city)} />
)}
</>
);
}

@ -1,6 +1,6 @@
import * as React from "react"; import * as React from "react";
import { IPlayer } from "../PersonObjects/IPlayer"; import { IPlayer } from "../PersonObjects/IPlayer";
import { dialogBoxCreate } from "../../utils/DialogBox"; import { dialogBoxCreate } from "../ui/React/DialogBox";
const gainLimit = 10e9; const gainLimit = 10e9;

@ -12,7 +12,7 @@ import { SpecialServerNames } from "./Server/SpecialServerIps";
import { Server } from "./Server/Server"; import { Server } from "./Server/Server";
import { HacknetServer } from "./Hacknet/HacknetServer"; import { HacknetServer } from "./Hacknet/HacknetServer";
import { getRandomInt } from "../utils/helpers/getRandomInt"; import { getRandomInt } from "./utils/helpers/getRandomInt";
export function generateRandomContract(): void { export function generateRandomContract(): void {
// First select a random problem type // First select a random problem type

@ -2,7 +2,7 @@ import { codingContractTypesMetadata, DescriptionFunc, GeneratorFunc, SolverFunc
import { IMap } from "./types"; import { IMap } from "./types";
import { Generic_fromJSON, Generic_toJSON, Reviver } from "../utils/JSONReviver"; import { Generic_fromJSON, Generic_toJSON, Reviver } from "./utils/JSONReviver";
import { createPopup, removePopup } from "./ui/React/createPopup"; import { createPopup, removePopup } from "./ui/React/createPopup";
import { CodingContractPopup } from "./ui/React/CodingContractPopup"; import { CodingContractPopup } from "./ui/React/CodingContractPopup";

@ -2,7 +2,7 @@
import { companiesMetadata } from "./data/CompaniesMetadata"; import { companiesMetadata } from "./data/CompaniesMetadata";
import { Company, IConstructorParams } from "./Company"; import { Company, IConstructorParams } from "./Company";
import { IMap } from "../types"; import { IMap } from "../types";
import { Reviver } from "../../utils/JSONReviver"; import { Reviver } from "../utils/JSONReviver";
export let Companies: IMap<Company> = {}; export let Companies: IMap<Company> = {};

@ -4,7 +4,7 @@ import * as posNames from "./data/companypositionnames";
import { CONSTANTS } from "../Constants"; import { CONSTANTS } from "../Constants";
import { IMap } from "../types"; import { IMap } from "../types";
import { Generic_fromJSON, Generic_toJSON, Reviver } from "../../utils/JSONReviver"; import { Generic_fromJSON, Generic_toJSON, Reviver } from "../utils/JSONReviver";
export interface IConstructorParams { export interface IConstructorParams {
name: string; name: string;

@ -9,6 +9,7 @@ export const CONSTANTS: {
MaxSkillLevel: number; MaxSkillLevel: number;
MilliPerCycle: number; MilliPerCycle: number;
CorpFactionRepRequirement: number; CorpFactionRepRequirement: number;
BaseFocusBonus: number;
BaseCostFor1GBOfRamHome: number; BaseCostFor1GBOfRamHome: number;
BaseCostFor1GBOfRamServer: number; BaseCostFor1GBOfRamServer: number;
TravelCost: number; TravelCost: number;
@ -285,6 +286,7 @@ export const CONSTANTS: {
GameCyclesPerFiveMinutes: 300000 / 200, GameCyclesPerFiveMinutes: 300000 / 200,
// Player Work & Action // Player Work & Action
BaseFocusBonus: 0.8,
FactionWorkHacking: "Faction Hacking Work", FactionWorkHacking: "Faction Hacking Work",
FactionWorkField: "Faction Field Work", FactionWorkField: "Faction Field Work",
FactionWorkSecurity: "Faction Security Work", FactionWorkSecurity: "Faction Security Work",

@ -10,9 +10,9 @@ import { showLiterature } from "../Literature/LiteratureHelpers";
import { LiteratureNames } from "../Literature/data/LiteratureNames"; import { LiteratureNames } from "../Literature/data/LiteratureNames";
import { IPlayer } from "../PersonObjects/IPlayer"; import { IPlayer } from "../PersonObjects/IPlayer";
import { dialogBoxCreate } from "../../utils/DialogBox"; import { dialogBoxCreate } from "../ui/React/DialogBox";
import { Reviver, Generic_toJSON, Generic_fromJSON } from "../../utils/JSONReviver"; import { Reviver, Generic_toJSON, Generic_fromJSON } from "../utils/JSONReviver";
import { isString } from "../../utils/helpers/isString"; import { isString } from "../utils/helpers/isString";
// UI Related Imports // UI Related Imports

@ -1,4 +1,4 @@
import { Generic_fromJSON, Generic_toJSON, Reviver } from "../../utils/JSONReviver"; import { Generic_fromJSON, Generic_toJSON, Reviver } from "../utils/JSONReviver";
// Array of all valid states // Array of all valid states
const AllCorporationStates: string[] = ["START", "PURCHASE", "PRODUCTION", "SALE", "EXPORT"]; const AllCorporationStates: string[] = ["START", "PURCHASE", "PRODUCTION", "SALE", "EXPORT"];

@ -1,11 +1,11 @@
import { CorporationConstants } from "./data/Constants"; import { CorporationConstants } from "./data/Constants";
import { getRandomInt } from "../../utils/helpers/getRandomInt"; import { getRandomInt } from "../utils/helpers/getRandomInt";
import { Generic_fromJSON, Generic_toJSON, Reviver } from "../../utils/JSONReviver"; import { Generic_fromJSON, Generic_toJSON, Reviver } from "../utils/JSONReviver";
import { createElement } from "../../utils/uiHelpers/createElement"; import { createElement } from "../ui/uiHelpers/createElement";
import { EmployeePositions } from "./EmployeePositions"; import { EmployeePositions } from "./EmployeePositions";
import { ICorporation } from "./ICorporation"; import { ICorporation } from "./ICorporation";
import { numeralWrapper } from "../ui/numeralFormat"; import { numeralWrapper } from "../ui/numeralFormat";
import { formatNumber } from "../../utils/StringHelperFunctions"; import { formatNumber } from "../utils/StringHelperFunctions";
import { OfficeSpace } from "./OfficeSpace"; import { OfficeSpace } from "./OfficeSpace";
import { IIndustry } from "./IIndustry"; import { IIndustry } from "./IIndustry";

@ -1,22 +1,22 @@
import { Reviver, Generic_toJSON, Generic_fromJSON } from "../../utils/JSONReviver"; import { Reviver, Generic_toJSON, Generic_fromJSON } from "../utils/JSONReviver";
import { CityName } from "../Locations/data/CityNames"; import { CityName } from "../Locations/data/CityNames";
import Decimal from "decimal.js"; import Decimal from "decimal.js";
import { Industries, IndustryStartingCosts, IndustryResearchTrees } from "./IndustryData"; import { Industries, IndustryStartingCosts, IndustryResearchTrees } from "./IndustryData";
import { CorporationConstants } from "./data/Constants"; import { CorporationConstants } from "./data/Constants";
import { EmployeePositions } from "./EmployeePositions"; import { EmployeePositions } from "./EmployeePositions";
import { Material } from "./Material"; import { Material } from "./Material";
import { getRandomInt } from "../../utils/helpers/getRandomInt"; import { getRandomInt } from "../utils/helpers/getRandomInt";
import { calculateEffectWithFactors } from "../utils/calculateEffectWithFactors"; import { calculateEffectWithFactors } from "../utils/calculateEffectWithFactors";
import { OfficeSpace } from "./OfficeSpace"; import { OfficeSpace } from "./OfficeSpace";
import { Product } from "./Product"; import { Product } from "./Product";
import { dialogBoxCreate } from "../../utils/DialogBox"; import { dialogBoxCreate } from "../ui/React/DialogBox";
import { isString } from "../../utils/helpers/isString"; import { isString } from "../utils/helpers/isString";
import { MaterialSizes } from "./MaterialSizes"; import { MaterialSizes } from "./MaterialSizes";
import { Warehouse } from "./Warehouse"; import { Warehouse } from "./Warehouse";
import { ICorporation } from "./ICorporation"; import { ICorporation } from "./ICorporation";
import { IIndustry } from "./IIndustry"; import { IIndustry } from "./IIndustry";
import { IndustryUpgrade, IndustryUpgrades } from "./IndustryUpgrades"; import { IndustryUpgrade, IndustryUpgrades } from "./IndustryUpgrades";
import { formatNumber } from "../../utils/StringHelperFunctions"; import { formatNumber } from "../utils/StringHelperFunctions";
interface IParams { interface IParams {
name?: string; name?: string;

@ -1,4 +1,4 @@
import { Generic_fromJSON, Generic_toJSON, Reviver } from "../../utils/JSONReviver"; import { Generic_fromJSON, Generic_toJSON, Reviver } from "../utils/JSONReviver";
import { Export } from "./Export"; import { Export } from "./Export";
interface IConstructorParams { interface IConstructorParams {

@ -1,8 +1,8 @@
import { EmployeePositions } from "./EmployeePositions"; import { EmployeePositions } from "./EmployeePositions";
import { CorporationConstants } from "./data/Constants"; import { CorporationConstants } from "./data/Constants";
import { getRandomInt } from "../../utils/helpers/getRandomInt"; import { getRandomInt } from "../utils/helpers/getRandomInt";
import { generateRandomString } from "../../utils/StringHelperFunctions"; import { generateRandomString } from "../utils/StringHelperFunctions";
import { Generic_fromJSON, Generic_toJSON, Reviver } from "../../utils/JSONReviver"; import { Generic_fromJSON, Generic_toJSON, Reviver } from "../utils/JSONReviver";
import { Employee } from "./Employee"; import { Employee } from "./Employee";
import { IIndustry } from "./IIndustry"; import { IIndustry } from "./IIndustry";
import { ICorporation } from "./ICorporation"; import { ICorporation } from "./ICorporation";

@ -6,8 +6,8 @@ import { ProductRatingWeights, IProductRatingWeight } from "./ProductRatingWeigh
import { createCityMap } from "../Locations/createCityMap"; import { createCityMap } from "../Locations/createCityMap";
import { IMap } from "../types"; import { IMap } from "../types";
import { Generic_fromJSON, Generic_toJSON, Reviver } from "../../utils/JSONReviver"; import { Generic_fromJSON, Generic_toJSON, Reviver } from "../utils/JSONReviver";
import { getRandomInt } from "../../utils/helpers/getRandomInt"; import { getRandomInt } from "../utils/helpers/getRandomInt";
interface IConstructorParams { interface IConstructorParams {
name?: string; name?: string;

@ -4,8 +4,8 @@ import { IIndustry } from "./IIndustry";
import { MaterialSizes } from "./MaterialSizes"; import { MaterialSizes } from "./MaterialSizes";
import { IMap } from "../types"; import { IMap } from "../types";
import { numeralWrapper } from "../ui/numeralFormat"; import { numeralWrapper } from "../ui/numeralFormat";
import { Generic_fromJSON, Generic_toJSON, Reviver } from "../../utils/JSONReviver"; import { Generic_fromJSON, Generic_toJSON, Reviver } from "../utils/JSONReviver";
import { exceptionAlert } from "../../utils/helpers/exceptionAlert"; import { exceptionAlert } from "../utils/helpers/exceptionAlert";
interface IConstructorParams { interface IConstructorParams {
corp?: ICorporation; corp?: ICorporation;

@ -0,0 +1,112 @@
import React, { useState } from "react";
import { Factions } from "../../Faction/Factions";
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 { useCorporation } from "./Context";
import Typography from "@mui/material/Typography";
import Button from "@mui/material/Button";
import MenuItem from "@mui/material/MenuItem";
import TextField from "@mui/material/TextField";
import Box from "@mui/material/Box";
import Select, { SelectChangeEvent } from "@mui/material/Select";
interface IProps {
open: boolean;
onClose: () => void;
}
export function BribeFactionModal(props: IProps): React.ReactElement {
const player = use.Player();
const corp = useCorporation();
const [money, setMoney] = useState<number | null>(0);
const [stock, setStock] = useState<number | null>(0);
const [selectedFaction, setSelectedFaction] = useState(
player.factions.length > 0 ? player.factions.filter((faction) => Factions[faction].getInfo().offersWork())[0] : "",
);
function onMoneyChange(event: React.ChangeEvent<HTMLInputElement>): void {
setMoney(parseFloat(event.target.value));
}
function onStockChange(event: React.ChangeEvent<HTMLInputElement>): void {
setStock(parseFloat(event.target.value));
}
function changeFaction(event: SelectChangeEvent<string>): void {
setSelectedFaction(event.target.value);
}
function repGain(money: number, stock: number): number {
return (money + stock * corp.sharePrice) / CorporationConstants.BribeToRepRatio;
}
function getRepText(money: number, stock: number): string {
if (money === 0 && stock === 0) return "";
if (isNaN(money) || isNaN(stock) || money < 0 || stock < 0) {
return "ERROR: Invalid value(s) entered";
} else if (corp.funds.lt(money)) {
return "ERROR: You do not have this much money to bribe with";
} else if (stock > corp.numShares) {
return "ERROR: You do not have this many shares to bribe with";
} else {
return (
"You will gain " +
numeralWrapper.formatReputation(repGain(money, stock)) +
" reputation with " +
selectedFaction +
" with this bribe"
);
}
}
function bribe(money: number, stock: number): void {
const fac = Factions[selectedFaction];
if (fac == null) {
dialogBoxCreate("ERROR: You must select a faction to bribe");
}
if (isNaN(money) || isNaN(stock) || money < 0 || stock < 0) {
} else if (corp.funds.lt(money)) {
} else if (stock > corp.numShares) {
} else {
const rep = repGain(money, stock);
dialogBoxCreate(
"You gained " + numeralWrapper.formatReputation(rep) + " reputation with " + fac.name + " by bribing them.",
);
fac.playerReputation += rep;
corp.funds = corp.funds.minus(money);
corp.numShares -= stock;
props.onClose();
}
}
return (
<Modal open={props.open} onClose={props.onClose}>
<Typography>
You can use Corporation funds or stock shares to bribe Faction Leaders in exchange for faction reputation.
</Typography>
<Box display="flex" alignItems="center">
<Typography>Faction:</Typography>
<Select variant="standard" value={selectedFaction} onChange={changeFaction}>
{player.factions.map((name: string) => {
const info = Factions[name].getInfo();
if (!info.offersWork()) return;
return (
<MenuItem key={name} value={name}>
{name}
</MenuItem>
);
})}
</Select>
</Box>
<Typography>{getRepText(money ? money : 0, stock ? stock : 0)}</Typography>
<TextField variant="standard" onChange={onMoneyChange} placeholder="Corporation funds" />
<TextField sx={{ mx: 1 }} variant="standard" onChange={onStockChange} placeholder="Stock Shares" />
<Button sx={{ mx: 1 }} onClick={() => bribe(money ? money : 0, stock ? stock : 0)}>
Bribe
</Button>
</Modal>
);
}

@ -1,109 +0,0 @@
import React, { useState } from "react";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { Factions } from "../../Faction/Factions";
import { CorporationConstants } from "../data/Constants";
import { numeralWrapper } from "../../ui/numeralFormat";
import { removePopup } from "../../ui/React/createPopup";
import { dialogBoxCreate } from "../../../utils/DialogBox";
import { ICorporation } from "../ICorporation";
interface IProps {
popupId: string;
corp: ICorporation;
player: IPlayer;
}
export function BribeFactionPopup(props: IProps): React.ReactElement {
const [money, setMoney] = useState<number | null>(0);
const [stock, setStock] = useState<number | null>(0);
const [selectedFaction, setSelectedFaction] = useState(
props.player.factions.length > 0 ? props.player.factions[0] : "",
);
function onMoneyChange(event: React.ChangeEvent<HTMLInputElement>): void {
setMoney(parseFloat(event.target.value));
}
function onStockChange(event: React.ChangeEvent<HTMLInputElement>): void {
setStock(parseFloat(event.target.value));
}
function changeFaction(event: React.ChangeEvent<HTMLSelectElement>): void {
setSelectedFaction(event.target.value);
}
function repGain(money: number, stock: number): number {
return (money + stock * props.corp.sharePrice) / CorporationConstants.BribeToRepRatio;
}
function getRepText(money: number, stock: number): string {
if (money === 0 && stock === 0) return "";
if (isNaN(money) || isNaN(stock) || money < 0 || stock < 0) {
return "ERROR: Invalid value(s) entered";
} else if (props.corp.funds.lt(money)) {
return "ERROR: You do not have this much money to bribe with";
} else if (stock > props.corp.numShares) {
return "ERROR: You do not have this many shares to bribe with";
} else {
return (
"You will gain " +
numeralWrapper.formatReputation(repGain(money, stock)) +
" reputation with " +
selectedFaction +
" with this bribe"
);
}
}
function bribe(money: number, stock: number): void {
const fac = Factions[selectedFaction];
if (fac == null) {
dialogBoxCreate("ERROR: You must select a faction to bribe");
}
if (isNaN(money) || isNaN(stock) || money < 0 || stock < 0) {
} else if (props.corp.funds.lt(money)) {
} else if (stock > props.corp.numShares) {
} else {
const rep = repGain(money, stock);
dialogBoxCreate(
"You gained " + numeralWrapper.formatReputation(rep) + " reputation with " + fac.name + " by bribing them.",
);
fac.playerReputation += rep;
props.corp.funds = props.corp.funds.minus(money);
props.corp.numShares -= stock;
removePopup(props.popupId);
}
}
return (
<>
<p>You can use Corporation funds or stock shares to bribe Faction Leaders in exchange for faction reputation.</p>
<select className="dropdown" style={{ margin: "3px" }} defaultValue={selectedFaction} onChange={changeFaction}>
{props.player.factions.map((name: string) => {
const info = Factions[name].getInfo();
if (!info.offersWork()) return;
return (
<option key={name} value={name}>
{name}
</option>
);
})}
</select>
<p>{getRepText(money ? money : 0, stock ? stock : 0)}</p>
<input
className="text-input"
onChange={onMoneyChange}
placeholder="Corporation funds"
style={{ margin: "5px" }}
/>
<input className="text-input" onChange={onStockChange} placeholder="Stock Shares" style={{ margin: "5px" }} />
<button
className="a-link-button"
onClick={() => bribe(money ? money : 0, stock ? stock : 0)}
style={{ display: "inline-block" }}
>
Bribe
</button>
</>
);
}

@ -1,20 +1,21 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { IPlayer } from "../../PersonObjects/IPlayer"; import { Modal } from "../../ui/React/Modal";
import { removePopup } from "../../ui/React/createPopup";
import { numeralWrapper } from "../../ui/numeralFormat"; import { numeralWrapper } from "../../ui/numeralFormat";
import { dialogBoxCreate } from "../../../utils/DialogBox"; import { dialogBoxCreate } from "../../ui/React/DialogBox";
import { ICorporation } from "../ICorporation"; import { use } from "../../ui/Context";
import { useCorporation } from "./Context";
interface IProps { interface IProps {
player: IPlayer; open: boolean;
popupId: string; onClose: () => void;
corp: ICorporation;
rerender: () => void; rerender: () => void;
} }
// Create a popup that lets the player buyback shares // Create a popup that lets the player buyback shares
// This is created when the player clicks the "Buyback Shares" button in the overview panel // This is created when the player clicks the "Buyback Shares" button in the overview panel
export function BuybackSharesPopup(props: IProps): React.ReactElement { export function BuybackSharesModal(props: IProps): React.ReactElement {
const player = use.Player();
const corp = useCorporation();
const [shares, setShares] = useState<number | null>(null); const [shares, setShares] = useState<number | null>(null);
function changeShares(event: React.ChangeEvent<HTMLInputElement>): void { function changeShares(event: React.ChangeEvent<HTMLInputElement>): void {
@ -22,38 +23,38 @@ export function BuybackSharesPopup(props: IProps): React.ReactElement {
else setShares(Math.round(parseFloat(event.target.value))); else setShares(Math.round(parseFloat(event.target.value)));
} }
const currentStockPrice = props.corp.sharePrice; const currentStockPrice = corp.sharePrice;
const buybackPrice = currentStockPrice * 1.1; const buybackPrice = currentStockPrice * 1.1;
function buy(): void { function buy(): void {
if (shares === null) return; if (shares === null) return;
const tempStockPrice = props.corp.sharePrice; const tempStockPrice = corp.sharePrice;
const buybackPrice = tempStockPrice * 1.1; const buybackPrice = tempStockPrice * 1.1;
if (isNaN(shares) || shares <= 0) { if (isNaN(shares) || shares <= 0) {
dialogBoxCreate("ERROR: Invalid value for number of shares"); dialogBoxCreate("ERROR: Invalid value for number of shares");
} else if (shares > props.corp.issuedShares) { } else if (shares > corp.issuedShares) {
dialogBoxCreate("ERROR: There are not this many oustanding shares to buy back"); dialogBoxCreate("ERROR: There are not this many oustanding shares to buy back");
} else if (shares * buybackPrice > props.player.money) { } else if (shares * buybackPrice > player.money) {
dialogBoxCreate( dialogBoxCreate(
"ERROR: You do not have enough money to purchase this many shares (you need " + "ERROR: You do not have enough money to purchase this many shares (you need " +
numeralWrapper.format(shares * buybackPrice, "$0.000a") + numeralWrapper.format(shares * buybackPrice, "$0.000a") +
")", ")",
); );
} else { } else {
props.corp.numShares += shares; corp.numShares += shares;
if (isNaN(props.corp.issuedShares)) { if (isNaN(corp.issuedShares)) {
console.warn("Corporation issuedShares is NaN: " + props.corp.issuedShares); console.warn("Corporation issuedShares is NaN: " + corp.issuedShares);
console.warn("Converting to number now"); console.warn("Converting to number now");
const res = props.corp.issuedShares; const res = corp.issuedShares;
if (isNaN(res)) { if (isNaN(res)) {
props.corp.issuedShares = 0; corp.issuedShares = 0;
} else { } else {
props.corp.issuedShares = res; corp.issuedShares = res;
} }
} }
props.corp.issuedShares -= shares; corp.issuedShares -= shares;
props.player.loseMoney(shares * buybackPrice); player.loseMoney(shares * buybackPrice);
removePopup(props.popupId); props.onClose();
props.rerender(); props.rerender();
} }
} }
@ -62,11 +63,11 @@ export function BuybackSharesPopup(props: IProps): React.ReactElement {
if (shares === null) return <></>; if (shares === null) return <></>;
if (isNaN(shares) || shares <= 0) { if (isNaN(shares) || shares <= 0) {
return <>ERROR: Invalid value entered for number of shares to buyback</>; return <>ERROR: Invalid value entered for number of shares to buyback</>;
} else if (shares > props.corp.issuedShares) { } else if (shares > corp.issuedShares) {
return ( return (
<> <>
There are not this many shares available to buy back. There are only{" "} There are not this many shares available to buy back. There are only{" "}
{numeralWrapper.formatBigNumber(props.corp.issuedShares)} outstanding shares. {numeralWrapper.formatBigNumber(corp.issuedShares)} outstanding shares.
</> </>
); );
} else { } else {
@ -83,7 +84,7 @@ export function BuybackSharesPopup(props: IProps): React.ReactElement {
} }
return ( return (
<> <Modal open={props.open} onClose={props.onClose}>
<p> <p>
Enter the number of outstanding shares you would like to buy back. These shares must be bought at a 10% premium. Enter the number of outstanding shares you would like to buy back. These shares must be bought at a 10% premium.
However, repurchasing shares from the market tends to lead to an increase in stock price. However, repurchasing shares from the market tends to lead to an increase in stock price.
@ -93,7 +94,7 @@ export function BuybackSharesPopup(props: IProps): React.ReactElement {
<br /> <br />
<br /> <br />
The current buyback price of your company's stock is {numeralWrapper.formatMoney(buybackPrice)}. Your company The current buyback price of your company's stock is {numeralWrapper.formatMoney(buybackPrice)}. Your company
currently has {numeralWrapper.formatBigNumber(props.corp.issuedShares)} outstanding stock shares. currently has {numeralWrapper.formatBigNumber(corp.issuedShares)} outstanding stock shares.
</p> </p>
<CostIndicator /> <CostIndicator />
<br /> <br />
@ -109,6 +110,6 @@ export function BuybackSharesPopup(props: IProps): React.ReactElement {
<button onClick={buy} className="a-link-button" style={{ display: "inline-block" }}> <button onClick={buy} className="a-link-button" style={{ display: "inline-block" }}>
Buy shares Buy shares
</button> </button>
</> </Modal>
); );
} }

@ -0,0 +1,10 @@
import React, { useContext } from "react";
import { ICorporation } from "../ICorporation";
export const Context: {
Corporation: React.Context<ICorporation>;
} = {
Corporation: React.createContext<ICorporation>({} as ICorporation),
};
export const useCorporation = () => useContext(Context.Corporation);

@ -2,41 +2,16 @@
// These are the tabs at the top of the UI that let you switch to different // These are the tabs at the top of the UI that let you switch to different
// divisions, see an overview of your corporation, or create a new industry // divisions, see an overview of your corporation, or create a new industry
import React, { useState, useEffect } from "react"; import React, { useState, useEffect } from "react";
import { HeaderTab } from "./HeaderTab";
import { IIndustry } from "../IIndustry"; import { IIndustry } from "../IIndustry";
import { NewIndustryPopup } from "./NewIndustryPopup";
import { createPopup } from "../../ui/React/createPopup";
import { ICorporation } from "../ICorporation";
import { MainPanel } from "./MainPanel"; import { MainPanel } from "./MainPanel";
import { Industries } from "../IndustryData"; import { Industries } from "../IndustryData";
import { ExpandIndustryTab } from "./ExpandIndustryTab";
import { use } from "../../ui/Context"; import { use } from "../../ui/Context";
import { Context } from "./Context";
import { Overview } from "./Overview";
interface IExpandButtonProps { import Tabs from "@mui/material/Tabs";
corp: ICorporation; import Tab from "@mui/material/Tab";
setDivisionName: (name: string) => void;
}
function ExpandButton(props: IExpandButtonProps): React.ReactElement {
const allIndustries = Object.keys(Industries).sort();
const possibleIndustries = allIndustries
.filter(
(industryType: string) =>
props.corp.divisions.find((division: IIndustry) => division.type === industryType) === undefined,
)
.sort();
if (possibleIndustries.length === 0) return <></>;
function openNewIndustryPopup(): void {
const popupId = "cmpy-mgmt-expand-industry-popup";
createPopup(popupId, NewIndustryPopup, {
corp: props.corp,
setDivisionName: props.setDivisionName,
popupId: popupId,
});
}
return <HeaderTab current={false} onClick={openNewIndustryPopup} text={"Expand into new Industry"} />;
}
export function CorporationRoot(): React.ReactElement { export function CorporationRoot(): React.ReactElement {
const player = use.Player(); const player = use.Player();
@ -46,33 +21,37 @@ export function CorporationRoot(): React.ReactElement {
function rerender(): void { function rerender(): void {
setRerender((old) => !old); setRerender((old) => !old);
} }
const [divisionName, setDivisionName] = useState("Overview"); const [divisionName, setDivisionName] = useState<string | number>("Overview");
function handleChange(event: React.SyntheticEvent, tab: string | number): void {
setDivisionName(tab);
}
useEffect(() => { useEffect(() => {
const id = setInterval(rerender, 1000); const id = setInterval(rerender, 200);
return () => clearInterval(id); return () => clearInterval(id);
}, []); }, []);
const canExpand =
Object.keys(Industries).filter(
(industryType: string) =>
corporation.divisions.find((division: IIndustry) => division.type === industryType) === undefined,
).length > 0;
return ( return (
<div className="cmpy-mgmt-container"> <Context.Corporation.Provider value={corporation}>
<div> <div className="cmpy-mgmt-container">
<HeaderTab <Tabs variant="fullWidth" value={divisionName} onChange={handleChange}>
current={divisionName === "Overview"} <Tab label={corporation.name} value={"Overview"} />
key={"overview"} {corporation.divisions.map((div) => (
onClick={() => setDivisionName("Overview")} <Tab key={div.name} label={div.name} value={div.name} />
text={corporation.name} ))}
/> {canExpand && <Tab label={"Expand"} value={-1} />}
{corporation.divisions.map((division: IIndustry) => ( </Tabs>
<HeaderTab {divisionName === "Overview" && <Overview rerender={rerender} />}
current={division.name === divisionName} {divisionName === -1 && <ExpandIndustryTab setDivisionName={setDivisionName} />}
key={division.name} {typeof divisionName === "string" && divisionName !== "Overview" && (
onClick={() => setDivisionName(division.name)} <MainPanel rerender={rerender} divisionName={divisionName + ""} />
text={division.name} )}
/>
))}
<ExpandButton corp={corporation} setDivisionName={setDivisionName} />
</div> </div>
<MainPanel rerender={rerender} corp={corporation} divisionName={divisionName} player={player} /> </Context.Corporation.Provider>
</div>
); );
} }

@ -0,0 +1,75 @@
import React, { useState } from "react";
import { Money } from "../../ui/React/Money";
import { Modal } from "../../ui/React/Modal";
import { use } from "../../ui/Context";
import Typography from "@mui/material/Typography";
import Button from "@mui/material/Button";
import TextField from "@mui/material/TextField";
interface IProps {
open: boolean;
onClose: () => void;
}
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()) {
props.onClose();
return <></>;
}
const [name, setName] = useState("");
function onChange(event: React.ChangeEvent<HTMLInputElement>): void {
setName(event.target.value);
}
function selfFund(): void {
if (!canSelfFund) {
return;
}
if (name == "") {
return;
}
player.startCorporation(name);
player.loseMoney(150e9);
props.onClose();
router.toCorporation();
}
function seed(): void {
if (name == "") {
return;
}
player.startCorporation(name, 500e6);
props.onClose();
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. This $150b
can either be self-funded, or you can obtain the seed money from the government in exchange for 500 million
shares
<br />
<br />
If you would like to start one, please enter a name for your corporation below:
</Typography>
<TextField autoFocus={true} variant="standard" placeholder="Corporation Name" onChange={onChange} value={name} />
<Button onClick={seed} disabled={name == ""}>
Use seed money
</Button>
<Button onClick={selfFund} disabled={name == "" || !canSelfFund}>
Self-Fund (<Money money={150e9} player={player} />)
</Button>
</Modal>
);
}

@ -1,83 +0,0 @@
import React, { useState } from "react";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { removePopup } from "../../ui/React/createPopup";
import { Money } from "../../ui/React/Money";
import { dialogBoxCreate } from "../../../utils/DialogBox";
import { IRouter } from "../../ui/Router";
interface IProps {
player: IPlayer;
popupId: string;
router: IRouter;
}
export function CreateCorporationPopup(props: IProps): React.ReactElement {
if (!props.player.canAccessCorporation() || props.player.hasCorporation()) {
removePopup(props.popupId);
return <></>;
}
const [name, setName] = useState("");
function onChange(event: React.ChangeEvent<HTMLInputElement>): void {
setName(event.target.value);
}
function selfFund(): void {
if (!props.player.canAfford(150e9)) {
dialogBoxCreate("You don't have enough money to create a corporation! You need $150b.");
return;
}
if (name == "") {
dialogBoxCreate("Invalid company name!");
return;
}
props.player.startCorporation(name);
props.player.loseMoney(150e9);
dialogBoxCreate(
"Congratulations! You just self-funded your own corporation. You can visit " +
"and manage your company in the City.",
);
removePopup(props.popupId);
props.router.toCorporation();
}
function seed(): void {
if (name == "") {
dialogBoxCreate("Invalid company name!");
return;
}
props.player.startCorporation(name, 500e6);
dialogBoxCreate(
"Congratulations! You just started your own corporation with government seed money. " +
"You can visit and manage your company in the City.",
);
removePopup(props.popupId);
props.router.toCorporation();
}
return (
<>
<p>
Would you like to start a corporation? This will require $150b for registration and initial funding. This $150b
can either be self-funded, or you can obtain the seed money from the government in exchange for 500 million
shares
<br />
<br />
If you would like to start one, please enter a name for your corporation below:
</p>
<input autoFocus={true} className="text-input" placeholder="Corporation Name" onChange={onChange} value={name} />
<button className="std-button" onClick={seed} disabled={name == ""}>
Use seed money
</button>
<button className="std-button" onClick={selfFund} disabled={name == "" || !props.player.canAfford(150e9)}>
Self-Fund (<Money money={150e9} player={props.player} />)
</button>
</>
);
}

@ -0,0 +1,90 @@
import React, { useState } from "react";
import { dialogBoxCreate } from "../../ui/React/DialogBox";
import { Industries, IndustryDescriptions } from "../IndustryData";
import { useCorporation } from "./Context";
import { IIndustry } from "../IIndustry";
import { NewIndustry } from "../Actions";
import Typography from "@mui/material/Typography";
import Button from "@mui/material/Button";
import TextField from "@mui/material/TextField";
import MenuItem from "@mui/material/MenuItem";
import Box from "@mui/material/Box";
import Select, { SelectChangeEvent } from "@mui/material/Select";
interface IProps {
setDivisionName: (name: string) => void;
}
export function ExpandIndustryTab(props: IProps): React.ReactElement {
const corp = useCorporation();
const allIndustries = Object.keys(Industries).sort();
const possibleIndustries = allIndustries
.filter(
(industryType: string) =>
corp.divisions.find((division: IIndustry) => division.type === industryType) === undefined,
)
.sort();
const [industry, setIndustry] = useState(possibleIndustries.length > 0 ? possibleIndustries[0] : "");
const [name, setName] = useState("");
function newIndustry(): void {
try {
NewIndustry(corp, industry, name);
} catch (err) {
dialogBoxCreate(err + "");
return;
}
// Set routing to the new division so that the UI automatically switches to it
props.setDivisionName(name);
}
function onNameChange(event: React.ChangeEvent<HTMLInputElement>): void {
// [a-zA-Z0-9-_]
setName(event.target.value);
}
function onKeyDown(event: React.KeyboardEvent<HTMLInputElement>): void {
if (event.keyCode === 13) newIndustry();
}
function onIndustryChange(event: SelectChangeEvent<string>): void {
setIndustry(event.target.value);
}
const desc = IndustryDescriptions[industry];
if (desc === undefined) throw new Error(`Trying to create an industry that doesn't exists: '${industry}'`);
return (
<>
<Typography>Create a new division to expand into a new industry:</Typography>
<Select variant="standard" value={industry} onChange={onIndustryChange}>
{possibleIndustries.map((industry: string) => (
<MenuItem key={industry} value={industry}>
{industry}
</MenuItem>
))}
</Select>
<Typography>{desc(corp)}</Typography>
<br />
<br />
<Typography>Division name:</Typography>
<Box display="flex" alignItems="center">
<TextField
variant="standard"
autoFocus={true}
value={name}
onChange={onNameChange}
onKeyDown={onKeyDown}
type="text"
/>
<Button sx={{ mx: 1 }} onClick={newIndustry}>
Create Division
</Button>
</Box>
</>
);
}

@ -2,7 +2,7 @@ import React, { useRef } from "react";
import { IIndustry } from "../IIndustry"; import { IIndustry } from "../IIndustry";
import { CorporationConstants } from "../data/Constants"; import { CorporationConstants } from "../data/Constants";
import { removePopup } from "../../ui/React/createPopup"; import { removePopup } from "../../ui/React/createPopup";
import { dialogBoxCreate } from "../../../utils/DialogBox"; import { dialogBoxCreate } from "../../ui/React/DialogBox";
import { ICorporation } from "../ICorporation"; import { ICorporation } from "../ICorporation";
import { NewCity } from "../Actions"; import { NewCity } from "../Actions";
import { MoneyCost } from "./MoneyCost"; import { MoneyCost } from "./MoneyCost";

@ -1,5 +1,5 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { dialogBoxCreate } from "../../../utils/DialogBox"; import { dialogBoxCreate } from "../../ui/React/DialogBox";
import { removePopup } from "../../ui/React/createPopup"; import { removePopup } from "../../ui/React/createPopup";
import { ICorporation } from "../ICorporation"; import { ICorporation } from "../ICorporation";
import { Material } from "../Material"; import { Material } from "../Material";

@ -1,23 +1,25 @@
import React from "react"; import React from "react";
import { removePopup } from "../../ui/React/createPopup";
import { numeralWrapper } from "../../ui/numeralFormat"; import { numeralWrapper } from "../../ui/numeralFormat";
import { CorporationConstants } from "../data/Constants"; import { CorporationConstants } from "../data/Constants";
import { ICorporation } from "../ICorporation"; import { Modal } from "../../ui/React/Modal";
import { IPlayer } from "../../PersonObjects/IPlayer"; import { useCorporation } from "./Context";
import Typography from "@mui/material/Typography";
import Button from "@mui/material/Button";
interface IProps { interface IProps {
corp: ICorporation; open: boolean;
popupId: string; onClose: () => void;
player: IPlayer;
rerender: () => void; rerender: () => void;
} }
// Create a popup that lets the player manage exports // Create a popup that lets the player manage exports
export function FindInvestorsPopup(props: IProps): React.ReactElement { export function FindInvestorsModal(props: IProps): React.ReactElement {
const val = props.corp.determineValuation(); const corp = useCorporation();
const val = corp.determineValuation();
let percShares = 0; let percShares = 0;
let roundMultiplier = 4; let roundMultiplier = 4;
switch (props.corp.fundingRound) { switch (corp.fundingRound) {
case 0: //Seed case 0: //Seed
percShares = 0.1; percShares = 0.1;
roundMultiplier = 4; roundMultiplier = 4;
@ -41,15 +43,15 @@ export function FindInvestorsPopup(props: IProps): React.ReactElement {
const investShares = Math.floor(CorporationConstants.INITIALSHARES * percShares); const investShares = Math.floor(CorporationConstants.INITIALSHARES * percShares);
function findInvestors(): void { function findInvestors(): void {
props.corp.fundingRound++; corp.fundingRound++;
props.corp.addFunds(funding); corp.addFunds(funding);
props.corp.numShares -= investShares; corp.numShares -= investShares;
props.rerender(); props.rerender();
removePopup(props.popupId); props.onClose();
} }
return ( return (
<> <Modal open={props.open} onClose={props.onClose}>
<p> <Typography>
An investment firm has offered you {numeralWrapper.formatMoney(funding)} in funding in exchange for a{" "} An investment firm has offered you {numeralWrapper.formatMoney(funding)} in funding in exchange for a{" "}
{numeralWrapper.format(percShares * 100, "0.000a")}% stake in the company ( {numeralWrapper.format(percShares * 100, "0.000a")}% stake in the company (
{numeralWrapper.format(investShares, "0.000a")} shares). {numeralWrapper.format(investShares, "0.000a")} shares).
@ -59,10 +61,8 @@ export function FindInvestorsPopup(props: IProps): React.ReactElement {
<br /> <br />
<br /> <br />
Hint: Investment firms will offer more money if your corporation is turning a profit Hint: Investment firms will offer more money if your corporation is turning a profit
</p> </Typography>
<button onClick={findInvestors} className="std-button"> <Button onClick={findInvestors}>Accept</Button>
Accept </Modal>
</button>
</>
); );
} }

@ -0,0 +1,81 @@
import React, { useState } from "react";
import { dialogBoxCreate } from "../../ui/React/DialogBox";
import { Modal } from "../../ui/React/Modal";
import { numeralWrapper } from "../../ui/numeralFormat";
import { useCorporation } from "./Context";
import Typography from "@mui/material/Typography";
import Button from "@mui/material/Button";
import TextField from "@mui/material/TextField";
import Box from "@mui/material/Box";
interface IProps {
open: boolean;
onClose: () => void;
rerender: () => void;
}
// Create a popup that lets the player manage exports
export function GoPublicModal(props: IProps): React.ReactElement {
const corp = useCorporation();
const [shares, setShares] = useState("");
const initialSharePrice = corp.determineValuation() / corp.totalShares;
function goPublic(): void {
const numShares = parseFloat(shares);
const initialSharePrice = corp.determineValuation() / corp.totalShares;
if (isNaN(numShares)) {
dialogBoxCreate("Invalid value for number of issued shares");
return;
}
if (numShares > corp.numShares) {
dialogBoxCreate("Error: You don't have that many shares to issue!");
return;
}
corp.public = true;
corp.sharePrice = initialSharePrice;
corp.issuedShares = numShares;
corp.numShares -= numShares;
corp.addFunds(numShares * initialSharePrice);
props.rerender();
dialogBoxCreate(
`You took your ${corp.name} public and earned ` +
`${numeralWrapper.formatMoney(numShares * initialSharePrice)} in your IPO`,
);
props.onClose();
}
function onKeyDown(event: React.KeyboardEvent<HTMLInputElement>): void {
if (event.keyCode === 13) goPublic();
}
function onChange(event: React.ChangeEvent<HTMLInputElement>): void {
setShares(event.target.value);
}
return (
<Modal open={props.open} onClose={props.onClose}>
<Typography>
Enter the number of shares you would like to issue for your IPO. These shares will be publicly sold and you will
no longer own them. Your Corporation will receive {numeralWrapper.formatMoney(initialSharePrice)} per share (the
IPO money will be deposited directly into your Corporation's funds).
<br />
<br />
You have a total of {numeralWrapper.format(corp.numShares, "0.000a")} of shares that you can issue.
</Typography>
<Box display="flex" alignItems="center">
<TextField
variant="standard"
value={shares}
onChange={onChange}
autoFocus
type="number"
placeholder="Shares to issue"
onKeyDown={onKeyDown}
/>
<Button sx={{ mx: 1 }} onClick={goPublic}>
Go Public
</Button>
</Box>
</Modal>
);
}

@ -1,76 +0,0 @@
import React, { useState } from "react";
import { dialogBoxCreate } from "../../../utils/DialogBox";
import { removePopup } from "../../ui/React/createPopup";
import { numeralWrapper } from "../../ui/numeralFormat";
import { ICorporation } from "../ICorporation";
import { IPlayer } from "../../PersonObjects/IPlayer";
interface IProps {
corp: ICorporation;
popupId: string;
player: IPlayer;
rerender: () => void;
}
// Create a popup that lets the player manage exports
export function GoPublicPopup(props: IProps): React.ReactElement {
const [shares, setShares] = useState("");
const initialSharePrice = props.corp.determineValuation() / props.corp.totalShares;
function goPublic(): void {
const numShares = parseFloat(shares);
const initialSharePrice = props.corp.determineValuation() / props.corp.totalShares;
if (isNaN(numShares)) {
dialogBoxCreate("Invalid value for number of issued shares");
return;
}
if (numShares > props.corp.numShares) {
dialogBoxCreate("Error: You don't have that many shares to issue!");
return;
}
props.corp.public = true;
props.corp.sharePrice = initialSharePrice;
props.corp.issuedShares = numShares;
props.corp.numShares -= numShares;
props.corp.addFunds(numShares * initialSharePrice);
props.rerender();
dialogBoxCreate(
`You took your ${props.corp.name} public and earned ` +
`${numeralWrapper.formatMoney(numShares * initialSharePrice)} in your IPO`,
);
removePopup(props.popupId);
}
function onKeyDown(event: React.KeyboardEvent<HTMLInputElement>): void {
if (event.keyCode === 13) goPublic();
}
function onChange(event: React.ChangeEvent<HTMLInputElement>): void {
setShares(event.target.value);
}
return (
<>
<p>
Enter the number of shares you would like to issue for your IPO. These shares will be publicly sold and you will
no longer own them. Your Corporation will receive {numeralWrapper.formatMoney(initialSharePrice)} per share (the
IPO money will be deposited directly into your Corporation's funds).
<br />
<br />
You have a total of {numeralWrapper.format(props.corp.numShares, "0.000a")} of shares that you can issue.
</p>
<input
className="text-input"
value={shares}
onChange={onChange}
autoFocus={true}
type="number"
placeholder="Shares to issue"
onKeyDown={onKeyDown}
/>
<button className="std-button" onClick={goPublic}>
Go Public
</button>
</>
);
}

@ -5,10 +5,10 @@ import { CorporationConstants } from "../data/Constants";
import { ICorporation } from "../ICorporation"; import { ICorporation } from "../ICorporation";
import { OfficeSpace } from "../OfficeSpace"; import { OfficeSpace } from "../OfficeSpace";
import { IPlayer } from "../../PersonObjects/IPlayer"; import { IPlayer } from "../../PersonObjects/IPlayer";
import { getRandomInt } from "../../../utils/helpers/getRandomInt"; import { getRandomInt } from "../../utils/helpers/getRandomInt";
import { formatNumber } from "../../../utils/StringHelperFunctions"; import { formatNumber } from "../../utils/StringHelperFunctions";
import { Employee } from "../Employee"; import { Employee } from "../Employee";
import { dialogBoxCreate } from "../../../utils/DialogBox"; import { dialogBoxCreate } from "../../ui/React/DialogBox";
interface INameEmployeeProps { interface INameEmployeeProps {
office: OfficeSpace; office: OfficeSpace;

@ -9,7 +9,7 @@ import { EmployeePositions } from "../EmployeePositions";
import { numeralWrapper } from "../../ui/numeralFormat"; import { numeralWrapper } from "../../ui/numeralFormat";
import { getSelectText } from "../../../utils/uiHelpers/getSelectData"; import { getSelectText } from "../../ui/uiHelpers/getSelectData";
import { createPopup } from "../../ui/React/createPopup"; import { createPopup } from "../../ui/React/createPopup";
import { UpgradeOfficeSizePopup } from "./UpgradeOfficeSizePopup"; import { UpgradeOfficeSizePopup } from "./UpgradeOfficeSizePopup";
import { HireEmployeePopup } from "./HireEmployeePopup"; import { HireEmployeePopup } from "./HireEmployeePopup";

@ -7,8 +7,8 @@ import { Industries } from "../IndustryData";
import { IndustryUpgrades } from "../IndustryUpgrades"; import { IndustryUpgrades } from "../IndustryUpgrades";
import { IIndustry } from "../IIndustry"; import { IIndustry } from "../IIndustry";
import { numeralWrapper } from "../../ui/numeralFormat"; import { numeralWrapper } from "../../ui/numeralFormat";
import { dialogBoxCreate } from "../../../utils/DialogBox"; import { dialogBoxCreate } from "../../ui/React/DialogBox";
import { createProgressBarText } from "../../../utils/helpers/createProgressBarText"; import { createProgressBarText } from "../../utils/helpers/createProgressBarText";
import { MakeProductPopup } from "./MakeProductPopup"; import { MakeProductPopup } from "./MakeProductPopup";
import { ResearchPopup } from "./ResearchPopup"; import { ResearchPopup } from "./ResearchPopup";
import { createPopup } from "../../ui/React/createPopup"; import { createPopup } from "../../ui/React/createPopup";

@ -20,7 +20,7 @@ import { SmartSupplyPopup } from "./SmartSupplyPopup";
import { numeralWrapper } from "../../ui/numeralFormat"; import { numeralWrapper } from "../../ui/numeralFormat";
import { createPopup } from "../../ui/React/createPopup"; import { createPopup } from "../../ui/React/createPopup";
import { isString } from "../../../utils/helpers/isString"; import { isString } from "../../utils/helpers/isString";
import { ICorporation } from "../ICorporation"; import { ICorporation } from "../ICorporation";
import { IIndustry } from "../IIndustry"; import { IIndustry } from "../IIndustry";
import { IPlayer } from "../../PersonObjects/IPlayer"; import { IPlayer } from "../../PersonObjects/IPlayer";

@ -1,29 +1,34 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { removePopup } from "../../ui/React/createPopup"; import { dialogBoxCreate } from "../../ui/React/DialogBox";
import { dialogBoxCreate } from "../../../utils/DialogBox"; import { Modal } from "../../ui/React/Modal";
import { CorporationConstants } from "../data/Constants"; import { CorporationConstants } from "../data/Constants";
import { ICorporation } from "../ICorporation";
import { IssueDividends } from "../Actions"; import { IssueDividends } from "../Actions";
import { useCorporation } from "./Context";
import Typography from "@mui/material/Typography";
import TextField from "@mui/material/TextField";
import Button from "@mui/material/Button";
interface IProps { interface IProps {
popupId: string; open: boolean;
corp: ICorporation; onClose: () => void;
} }
// Create a popup that lets the player issue & manage dividends // Create a popup that lets the player issue & manage dividends
// This is created when the player clicks the "Issue Dividends" button in the overview panel // This is created when the player clicks the "Issue Dividends" button in the overview panel
export function IssueDividendsPopup(props: IProps): React.ReactElement { export function IssueDividendsModal(props: IProps): React.ReactElement {
const [percent, setPercent] = useState<number | null>(null); const corp = useCorporation();
const [percent, setPercent] = useState(0);
const canIssue = !isNaN(percent) && percent >= 0 && percent <= CorporationConstants.DividendMaxPercentage * 100;
function issueDividends(): void { function issueDividends(): void {
if (!canIssue) return;
if (percent === null) return; if (percent === null) return;
try { try {
IssueDividends(props.corp, percent / 100); IssueDividends(corp, percent / 100);
} catch (err) { } catch (err) {
dialogBoxCreate(err + ""); dialogBoxCreate(err + "");
} }
removePopup(props.popupId); props.onClose();
} }
function onKeyDown(event: React.KeyboardEvent<HTMLInputElement>): void { function onKeyDown(event: React.KeyboardEvent<HTMLInputElement>): void {
@ -31,13 +36,17 @@ export function IssueDividendsPopup(props: IProps): React.ReactElement {
} }
function onChange(event: React.ChangeEvent<HTMLInputElement>): void { function onChange(event: React.ChangeEvent<HTMLInputElement>): void {
if (event.target.value === "") setPercent(null); if (event.target.value === "") setPercent(0);
else setPercent(parseFloat(event.target.value)); else {
let p = parseFloat(event.target.value);
if (p > 50) p = 50;
setPercent(p);
}
} }
return ( return (
<> <Modal open={props.open} onClose={props.onClose}>
<p> <Typography>
Dividends are a distribution of a portion of the corporation's profits to the shareholders. This includes Dividends are a distribution of a portion of the corporation's profits to the shareholders. This includes
yourself, as well. yourself, as well.
<br /> <br />
@ -58,19 +67,19 @@ export function IssueDividendsPopup(props: IProps): React.ReactElement {
That means your corporation will gain $60m / sec in funds and the remaining $40m / sec will be paid as That means your corporation will gain $60m / sec in funds and the remaining $40m / sec will be paid as
dividends. Since your corporation starts with 1 billion shares, every shareholder will be paid $0.04 per share dividends. Since your corporation starts with 1 billion shares, every shareholder will be paid $0.04 per share
per second before taxes. per second before taxes.
</p> </Typography>
<input <TextField
autoFocus={true} variant="standard"
autoFocus
value={percent}
onChange={onChange} onChange={onChange}
onKeyDown={onKeyDown} onKeyDown={onKeyDown}
className="text-input"
placeholder="Dividend %" placeholder="Dividend %"
type="number" type="number"
style={{ margin: "5px" }}
/> />
<button onClick={issueDividends} className="std-button" style={{ display: "inline-block" }}> <Button disabled={!canIssue} sx={{ mx: 1 }} onClick={issueDividends}>
Allocate Dividend Percentage Allocate Dividend Percentage
</button> </Button>
</> </Modal>
); );
} }

@ -1,24 +1,27 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { numeralWrapper } from "../../ui/numeralFormat"; import { numeralWrapper } from "../../ui/numeralFormat";
import { dialogBoxCreate } from "../../../utils/DialogBox"; import { dialogBoxCreate } from "../../ui/React/DialogBox";
import { removePopup } from "../../ui/React/createPopup"; import { Modal } from "../../ui/React/Modal";
import { getRandomInt } from "../../../utils/helpers/getRandomInt"; import { getRandomInt } from "../../utils/helpers/getRandomInt";
import { CorporationConstants } from "../data/Constants"; import { CorporationConstants } from "../data/Constants";
import { ICorporation } from "../ICorporation"; import { useCorporation } from "./Context";
import Typography from "@mui/material/Typography";
import TextField from "@mui/material/TextField";
import Button from "@mui/material/Button";
interface IEffectTextProps { interface IEffectTextProps {
corp: ICorporation;
shares: number | null; shares: number | null;
} }
function EffectText(props: IEffectTextProps): React.ReactElement { function EffectText(props: IEffectTextProps): React.ReactElement {
const corp = useCorporation();
if (props.shares === null) return <></>; if (props.shares === null) return <></>;
const newSharePrice = Math.round(props.corp.sharePrice * 0.9); const newSharePrice = Math.round(corp.sharePrice * 0.9);
const maxNewSharesUnrounded = Math.round(props.corp.totalShares * 0.2); const maxNewSharesUnrounded = Math.round(corp.totalShares * 0.2);
const maxNewShares = maxNewSharesUnrounded - (maxNewSharesUnrounded % 1e6); const maxNewShares = maxNewSharesUnrounded - (maxNewSharesUnrounded % 1e6);
let newShares = props.shares; let newShares = props.shares;
if (isNaN(newShares)) { if (isNaN(newShares)) {
return <p>Invalid input</p>; return <Typography>Invalid input</Typography>;
} }
// Round to nearest ten-millionth // Round to nearest ten-millionth
@ -26,36 +29,37 @@ function EffectText(props: IEffectTextProps): React.ReactElement {
newShares = Math.round(newShares) * 10e6; newShares = Math.round(newShares) * 10e6;
if (newShares < 10e6) { if (newShares < 10e6) {
return <p>Must issue at least 10 million new shares</p>; return <Typography>Must issue at least 10 million new shares</Typography>;
} }
if (newShares > maxNewShares) { if (newShares > maxNewShares) {
return <p>You cannot issue that many shares</p>; return <Typography>You cannot issue that many shares</Typography>;
} }
return ( return (
<p> <Typography>
Issue ${numeralWrapper.format(newShares, "0.000a")} new shares for{" "} Issue ${numeralWrapper.format(newShares, "0.000a")} new shares for{" "}
{numeralWrapper.formatMoney(newShares * newSharePrice)}? {numeralWrapper.formatMoney(newShares * newSharePrice)}?
</p> </Typography>
); );
} }
interface IProps { interface IProps {
corp: ICorporation; open: boolean;
popupId: string; onClose: () => void;
} }
// Create a popup that lets the player issue new shares // Create a popup that lets the player issue new shares
// This is created when the player clicks the "Issue New Shares" buttons in the overview panel // This is created when the player clicks the "Issue New Shares" buttons in the overview panel
export function IssueNewSharesPopup(props: IProps): React.ReactElement { export function IssueNewSharesModal(props: IProps): React.ReactElement {
const corp = useCorporation();
const [shares, setShares] = useState<number | null>(null); const [shares, setShares] = useState<number | null>(null);
const maxNewSharesUnrounded = Math.round(props.corp.totalShares * 0.2); const maxNewSharesUnrounded = Math.round(corp.totalShares * 0.2);
const maxNewShares = maxNewSharesUnrounded - (maxNewSharesUnrounded % 1e6); const maxNewShares = maxNewSharesUnrounded - (maxNewSharesUnrounded % 1e6);
function issueNewShares(): void { function issueNewShares(): void {
if (shares === null) return; if (shares === null) return;
const newSharePrice = Math.round(props.corp.sharePrice * 0.9); const newSharePrice = Math.round(corp.sharePrice * 0.9);
let newShares = shares; let newShares = shares;
if (isNaN(newShares)) { if (isNaN(newShares)) {
dialogBoxCreate("Invalid input for number of new shares"); dialogBoxCreate("Invalid input for number of new shares");
@ -71,8 +75,8 @@ export function IssueNewSharesPopup(props: IProps): React.ReactElement {
} }
const profit = newShares * newSharePrice; const profit = newShares * newSharePrice;
props.corp.issueNewSharesCooldown = CorporationConstants.IssueNewSharesCooldown; corp.issueNewSharesCooldown = CorporationConstants.IssueNewSharesCooldown;
props.corp.totalShares += newShares; corp.totalShares += newShares;
// Determine how many are bought by private investors // Determine how many are bought by private investors
// Private investors get up to 50% at most // Private investors get up to 50% at most
@ -80,16 +84,15 @@ export function IssueNewSharesPopup(props: IProps): React.ReactElement {
let privateShares = getRandomInt(0, Math.round(newShares / 2)); let privateShares = getRandomInt(0, Math.round(newShares / 2));
privateShares = Math.round(privateShares / 1e6) * 1e6; privateShares = Math.round(privateShares / 1e6) * 1e6;
props.corp.issuedShares += newShares - privateShares; corp.issuedShares += newShares - privateShares;
props.corp.funds = props.corp.funds.plus(profit); corp.funds = corp.funds.plus(profit);
props.corp.immediatelyUpdateSharePrice(); corp.immediatelyUpdateSharePrice();
props.onClose();
removePopup(props.popupId);
dialogBoxCreate( dialogBoxCreate(
`Issued ${numeralWrapper.format(newShares, "0.000a")} and raised ` + `Issued ${numeralWrapper.format(newShares, "0.000a")} and raised ` +
`${numeralWrapper.formatMoney(profit)}. ${numeralWrapper.format(privateShares, "0.000a")} ` + `${numeralWrapper.formatMoney(profit)}. ${numeralWrapper.format(privateShares, "0.000a")} ` +
`of these shares were bought by private investors.<br><br>` + `of these shares were bought by private investors.<br><br>` +
`Stock price decreased to ${numeralWrapper.formatMoney(props.corp.sharePrice)}`, `Stock price decreased to ${numeralWrapper.formatMoney(corp.sharePrice)}`,
); );
} }
@ -103,8 +106,8 @@ export function IssueNewSharesPopup(props: IProps): React.ReactElement {
} }
return ( return (
<> <Modal open={props.open} onClose={props.onClose}>
<p> <Typography>
You can issue new equity shares (i.e. stocks) in order to raise capital for your corporation. You can issue new equity shares (i.e. stocks) in order to raise capital for your corporation.
<br /> <br />
<br /> <br />
@ -122,19 +125,12 @@ export function IssueNewSharesPopup(props: IProps): React.ReactElement {
When you choose to issue new equity, private shareholders have first priority for up to 50% of the new shares. When you choose to issue new equity, private shareholders have first priority for up to 50% of the new shares.
If they choose to exercise this option, these newly issued shares become private, restricted shares, which means If they choose to exercise this option, these newly issued shares become private, restricted shares, which means
you cannot buy them back. you cannot buy them back.
</p> </Typography>
<EffectText corp={props.corp} shares={shares} /> <EffectText shares={shares} />
<input <TextField variant="standard" autoFocus placeholder="# New Shares" onChange={onChange} onKeyDown={onKeyDown} />
className="text-input" <Button onClick={issueNewShares} sx={{ mx: 1 }}>
autoFocus={true}
placeholder="# New Shares"
style={{ margin: "5px" }}
onChange={onChange}
onKeyDown={onKeyDown}
/>
<button onClick={issueNewShares} className="std-button" style={{ display: "inline-block" }}>
Issue New Shares Issue New Shares
</button> </Button>
</> </Modal>
); );
} }

@ -1,23 +1,23 @@
// React components for the levelable upgrade buttons on the overview panel // React components for the levelable upgrade buttons on the overview panel
import React from "react"; import React from "react";
import { dialogBoxCreate } from "../../../utils/DialogBox"; import { dialogBoxCreate } from "../../ui/React/DialogBox";
import { ICorporation } from "../ICorporation";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { CorporationUpgrade } from "../data/CorporationUpgrades"; import { CorporationUpgrade } from "../data/CorporationUpgrades";
import { LevelUpgrade } from "../Actions"; import { LevelUpgrade } from "../Actions";
import { MoneyCost } from "./MoneyCost"; import { MoneyCost } from "./MoneyCost";
import { use } from "../../ui/Context";
import { useCorporation } from "./Context";
interface IProps { interface IProps {
upgrade: CorporationUpgrade; upgrade: CorporationUpgrade;
corp: ICorporation;
player: IPlayer;
rerender: () => void; rerender: () => void;
} }
export function LevelableUpgrade(props: IProps): React.ReactElement { export function LevelableUpgrade(props: IProps): React.ReactElement {
const player = use.Player();
const corp = useCorporation();
const data = props.upgrade; const data = props.upgrade;
const level = props.corp.upgrades[data[0]]; const level = corp.upgrades[data[0]];
const baseCost = data[1]; const baseCost = data[1];
const priceMult = data[2]; const priceMult = data[2];
@ -25,14 +25,14 @@ export function LevelableUpgrade(props: IProps): React.ReactElement {
const text = ( const text = (
<> <>
{data[4]} - <MoneyCost money={cost} corp={props.corp} /> {data[4]} - <MoneyCost money={cost} corp={corp} />
</> </>
); );
const tooltip = data[5]; const tooltip = data[5];
function onClick(): void { function onClick(): void {
if (props.corp.funds.lt(cost)) return; if (corp.funds.lt(cost)) return;
try { try {
LevelUpgrade(props.corp, props.upgrade); LevelUpgrade(corp, props.upgrade);
} catch (err) { } catch (err) {
dialogBoxCreate(err + ""); dialogBoxCreate(err + "");
} }

@ -5,42 +5,28 @@ import React from "react";
import { CityTabs } from "./CityTabs"; import { CityTabs } from "./CityTabs";
import { IIndustry } from "../IIndustry"; import { IIndustry } from "../IIndustry";
import { Overview } from "./Overview"; import { useCorporation } from "./Context";
import { use } from "../../ui/Context";
import { CityName } from "../../Locations/data/CityNames"; import { CityName } from "../../Locations/data/CityNames";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { ICorporation } from "../ICorporation";
interface IProps { interface IProps {
corp: ICorporation;
player: IPlayer;
divisionName: string; divisionName: string;
rerender: () => void; rerender: () => void;
} }
export function MainPanel(props: IProps): React.ReactElement { export function MainPanel(props: IProps): React.ReactElement {
const player = use.Player();
const corp = useCorporation();
const division = const division =
props.divisionName !== "Overview" props.divisionName !== "Overview"
? props.corp.divisions.find((division: IIndustry) => division.name === props.divisionName) ? corp.divisions.find((division: IIndustry) => division.name === props.divisionName)
: undefined; // use undefined because find returns undefined : undefined; // use undefined because find returns undefined
if (division === undefined) { if (division === undefined) throw new Error("Cannot find division");
return ( return (
<div id="cmpy-mgmt-panel"> <div id="cmpy-mgmt-panel">
<Overview {...props} /> <CityTabs rerender={props.rerender} division={division} corp={corp} city={CityName.Sector12} player={player} />
</div> </div>
); );
} else {
return (
<div id="cmpy-mgmt-panel">
<CityTabs
rerender={props.rerender}
division={division}
corp={props.corp}
city={CityName.Sector12}
player={props.player}
/>
</div>
);
}
} }

@ -1,5 +1,5 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { dialogBoxCreate } from "../../../utils/DialogBox"; import { dialogBoxCreate } from "../../ui/React/DialogBox";
import { removePopup } from "../../ui/React/createPopup"; import { removePopup } from "../../ui/React/createPopup";
import { Industries } from "../IndustryData"; import { Industries } from "../IndustryData";
import { ICorporation } from "../ICorporation"; import { ICorporation } from "../ICorporation";

@ -1,87 +0,0 @@
import React, { useState } from "react";
import { dialogBoxCreate } from "../../../utils/DialogBox";
import { removePopup } from "../../ui/React/createPopup";
import { Industries, IndustryDescriptions } from "../IndustryData";
import { ICorporation } from "../ICorporation";
import { IIndustry } from "../IIndustry";
import { NewIndustry } from "../Actions";
interface IProps {
corp: ICorporation;
popupId: string;
setDivisionName: (name: string) => void;
}
// Create a popup that lets the player create a new industry.
// This is created when the player clicks the "Expand into new Industry" header tab
export function NewIndustryPopup(props: IProps): React.ReactElement {
const allIndustries = Object.keys(Industries).sort();
const possibleIndustries = allIndustries
.filter(
(industryType: string) =>
props.corp.divisions.find((division: IIndustry) => division.type === industryType) === undefined,
)
.sort();
const [industry, setIndustry] = useState(possibleIndustries.length > 0 ? possibleIndustries[0] : "");
const [name, setName] = useState("");
function newIndustry(): void {
try {
NewIndustry(props.corp, industry, name);
} catch (err) {
dialogBoxCreate(err + "");
return;
}
// Set routing to the new division so that the UI automatically switches to it
props.setDivisionName(name);
removePopup(props.popupId);
}
function onNameChange(event: React.ChangeEvent<HTMLInputElement>): void {
setName(event.target.value);
}
function onKeyDown(event: React.KeyboardEvent<HTMLInputElement>): void {
if (event.keyCode === 13) newIndustry();
}
function onIndustryChange(event: React.ChangeEvent<HTMLSelectElement>): void {
setIndustry(event.target.value);
}
const desc = IndustryDescriptions[industry];
if (desc === undefined) throw new Error(`Trying to create an industry that doesn't exists: '${industry}'`);
return (
<>
<p>Create a new division to expand into a new industry:</p>
<select className="dropdown" defaultValue={industry} onChange={onIndustryChange}>
{possibleIndustries.map((industry: string) => (
<option key={industry} value={industry}>
{industry}
</option>
))}
</select>
<p>{desc(props.corp)}</p>
<br />
<br />
<p>Division name:</p>
<input
autoFocus={true}
value={name}
onChange={onNameChange}
onKeyDown={onKeyDown}
type="text"
className="text-input"
style={{ display: "block" }}
maxLength={30}
pattern="[a-zA-Z0-9-_]"
/>
<span onClick={newIndustry} className="popup-box-button">
Create Division
</span>
</>
);
}

@ -1,14 +1,15 @@
// React Component for displaying Corporation Overview info // React Component for displaying Corporation Overview info
import React from "react"; import React, { useState } from "react";
import { LevelableUpgrade } from "./LevelableUpgrade"; import { LevelableUpgrade } from "./LevelableUpgrade";
import { UnlockUpgrade } from "./UnlockUpgrade"; import { UnlockUpgrade } from "./UnlockUpgrade";
import { BribeFactionPopup } from "./BribeFactionPopup"; import { BribeFactionModal } from "./BribeFactionModal";
import { SellSharesPopup } from "./SellSharesPopup"; import { SellSharesModal } from "./SellSharesModal";
import { BuybackSharesPopup } from "./BuybackSharesPopup"; import { BuybackSharesModal } from "./BuybackSharesModal";
import { IssueDividendsPopup } from "./IssueDividendsPopup"; import { IssueDividendsModal } from "./IssueDividendsModal";
import { IssueNewSharesPopup } from "./IssueNewSharesPopup"; import { IssueNewSharesModal } from "./IssueNewSharesModal";
import { FindInvestorsPopup } from "./FindInvestorsPopup"; import { FindInvestorsModal } from "./FindInvestorsModal";
import { GoPublicPopup } from "./GoPublicPopup"; import { GoPublicModal } from "./GoPublicModal";
import { Factions } from "../../Faction/Factions";
import { CorporationConstants } from "../data/Constants"; import { CorporationConstants } from "../data/Constants";
import { CorporationUnlockUpgrade, CorporationUnlockUpgrades } from "../data/CorporationUnlockUpgrades"; import { CorporationUnlockUpgrade, CorporationUnlockUpgrades } from "../data/CorporationUnlockUpgrades";
@ -16,45 +17,53 @@ import { CorporationUpgrade, CorporationUpgrades } from "../data/CorporationUpgr
import { CONSTANTS } from "../../Constants"; import { CONSTANTS } from "../../Constants";
import { numeralWrapper } from "../../ui/numeralFormat"; import { numeralWrapper } from "../../ui/numeralFormat";
import { convertTimeMsToTimeElapsedString } from "../../../utils/StringHelperFunctions"; import { convertTimeMsToTimeElapsedString } from "../../utils/StringHelperFunctions";
import { createPopup } from "../../ui/React/createPopup";
import { Money } from "../../ui/React/Money"; import { Money } from "../../ui/React/Money";
import { IPlayer } from "../../PersonObjects/IPlayer"; import { use } from "../../ui/Context";
import { ICorporation } from "../ICorporation"; import { useCorporation } from "./Context";
import Typography from "@mui/material/Typography";
import Tooltip from "@mui/material/Tooltip";
import Button from "@mui/material/Button";
interface IProps { interface IProps {
corp: ICorporation;
player: IPlayer;
rerender: () => void; rerender: () => void;
} }
export function Overview({ corp, player, rerender }: IProps): React.ReactElement { export function Overview({ rerender }: IProps): React.ReactElement {
const player = use.Player();
const corp = useCorporation();
const profit: number = corp.revenue.minus(corp.expenses).toNumber(); const profit: number = corp.revenue.minus(corp.expenses).toNumber();
return ( return (
<div> <div>
<p> <Typography>
Total Funds: <Money money={corp.funds.toNumber()} /> Total Funds: <Money money={corp.funds.toNumber()} />
<br /> <br />
Total Revenue: <Money money={corp.revenue.toNumber()} /> / s<br /> Total Revenue: <Money money={corp.revenue.toNumber()} /> / s<br />
Total Expenses: <Money money={corp.expenses.toNumber()} /> / s Total Expenses: <Money money={corp.expenses.toNumber()} /> / s
<br /> <br />
Total Profits: <Money money={profit} /> / s<br /> Total Profits: <Money money={profit} /> / s<br />
<DividendsStats corp={corp} profit={profit} /> <DividendsStats profit={profit} />
Publicly Traded: {corp.public ? "Yes" : "No"} Publicly Traded: {corp.public ? "Yes" : "No"}
<br /> <br />
Owned Stock Shares: {numeralWrapper.format(corp.numShares, "0.000a")} Owned Stock Shares: {numeralWrapper.format(corp.numShares, "0.000a")}
<br /> <br />
Stock Price: {corp.public ? <Money money={corp.sharePrice} /> : "N/A"} Stock Price: {corp.public ? <Money money={corp.sharePrice} /> : "N/A"}
<br /> <br />
</p> </Typography>
<p className="tooltip"> <Tooltip
Total Stock Shares: {numeralWrapper.format(corp.totalShares, "0.000a")} disableInteractive
<span className="tooltiptext"> title={
Outstanding Shares: {numeralWrapper.format(corp.issuedShares, "0.000a")} <Typography>
<br /> Outstanding Shares: {numeralWrapper.format(corp.issuedShares, "0.000a")}
Private Shares: {numeralWrapper.format(corp.totalShares - corp.issuedShares - corp.numShares, "0.000a")} <br />
</span> Private Shares: {numeralWrapper.format(corp.totalShares - corp.issuedShares - corp.numShares, "0.000a")}
</p> </Typography>
}
>
<Typography className="tooltip">
Total Stock Shares: {numeralWrapper.format(corp.totalShares, "0.000a")}
</Typography>
</Tooltip>
<br /> <br />
<br /> <br />
<Mult name="Production Multiplier: " mult={corp.getProductionMultiplier()} /> <Mult name="Production Multiplier: " mult={corp.getProductionMultiplier()} />
@ -67,100 +76,78 @@ export function Overview({ corp, player, rerender }: IProps): React.ReactElement
<Mult name="Sales Multiplier: " mult={corp.getSalesMultiplier()} /> <Mult name="Sales Multiplier: " mult={corp.getSalesMultiplier()} />
<Mult name="Scientific Research Multiplier: " mult={corp.getScientificResearchMultiplier()} /> <Mult name="Scientific Research Multiplier: " mult={corp.getScientificResearchMultiplier()} />
<br /> <br />
<BonusTime corp={corp} /> <BonusTime />
<div> <div>
<Button <Tooltip
className="a-link-button" disableInteractive
display="inline-block" title={
onClick={() => corp.getStarterGuide(player)} <Typography>
text="Getting Started Guide" Get a copy of and read 'The Complete Handbook for Creating a Successful Corporation.' This is a .lit file
tooltip={ that guides you through the beginning of setting up a Corporation and provides some tips/pointers for
"Get a copy of and read 'The Complete Handbook for Creating a Successful Corporation.' " + helping you get started with managing it.
"This is a .lit file that guides you through the beginning of setting up a Corporation and " + </Typography>
"provides some tips/pointers for helping you get started with managing it."
} }
/> >
{corp.public ? ( <Button onClick={() => corp.getStarterGuide(player)}>Getting Started Guide</Button>
<PublicButtons corp={corp} player={player} rerender={rerender} /> </Tooltip>
) : ( {corp.public ? <PublicButtons rerender={rerender} /> : <PrivateButtons rerender={rerender} />}
<PrivateButtons corp={corp} player={player} rerender={rerender} /> <BribeButton />
)}
<BribeButton corp={corp} player={player} />
</div> </div>
<br /> <br />
<Upgrades corp={corp} player={player} rerender={rerender} /> <Upgrades rerender={rerender} />
</div> </div>
); );
} }
interface IPrivateButtonsProps { interface IPrivateButtonsProps {
corp: ICorporation;
player: IPlayer;
rerender: () => void; rerender: () => void;
} }
// Render the buttons for when your Corporation is still private // Render the buttons for when your Corporation is still private
function PrivateButtons({ corp, player, rerender }: IPrivateButtonsProps): React.ReactElement { function PrivateButtons({ rerender }: IPrivateButtonsProps): React.ReactElement {
function openFindInvestorsPopup(): void { const player = use.Player();
const popupId = "cmpy-mgmt-find-investors-popup"; const corp = useCorporation();
createPopup(popupId, FindInvestorsPopup, { const [findInvestorsopen, setFindInvestorsopen] = useState(false);
rerender, const [goPublicopen, setGoPublicopen] = useState(false);
player,
popupId,
corp: corp,
});
}
function openGoPublicPopup(): void {
const popupId = "cmpy-mgmt-go-public-popup";
createPopup(popupId, GoPublicPopup, {
rerender,
player,
popupId,
corp: corp,
});
}
const fundingAvailable = corp.fundingRound < 4; const fundingAvailable = corp.fundingRound < 4;
const findInvestorsClassName = fundingAvailable ? "std-button" : "a-link-button-inactive";
const findInvestorsTooltip = fundingAvailable const findInvestorsTooltip = fundingAvailable
? "Search for private investors who will give you startup funding in exchangefor equity (stock shares) in your company" ? "Search for private investors who will give you startup funding in exchange for equity (stock shares) in your company"
: undefined; : "";
return ( return (
<> <>
<Button <Tooltip disableInteractive title={<Typography>{findInvestorsTooltip}</Typography>}>
className={findInvestorsClassName} <Button disabled={!fundingAvailable} onClick={() => setFindInvestorsopen(true)}>
onClick={openFindInvestorsPopup} Find Investors
text="Find Investors" </Button>
tooltip={findInvestorsTooltip} </Tooltip>
display="inline-block" <Tooltip
/> disableInteractive
<Button title={
className="std-button" <Typography>
onClick={openGoPublicPopup} Become a publicly traded and owned entity. Going public involves issuing shares for an IPO. Once you are a
display="inline-block" public company, your shares will be traded on the stock market.
text="Go Public" </Typography>
tooltip={
"Become a publicly traded and owned entity. Going public " +
"involves issuing shares for an IPO. Once you are a public " +
"company, your shares will be traded on the stock market."
} }
/> >
<Button onClick={() => setGoPublicopen(true)}>Go Public</Button>
</Tooltip>
<FindInvestorsModal open={findInvestorsopen} onClose={() => setFindInvestorsopen(false)} rerender={rerender} />
<GoPublicModal open={goPublicopen} onClose={() => setGoPublicopen(false)} rerender={rerender} />
<br /> <br />
</> </>
); );
} }
interface IUpgradeProps { interface IUpgradeProps {
corp: ICorporation;
player: IPlayer;
rerender: () => void; rerender: () => void;
} }
// Render the UI for Corporation upgrades // Render the UI for Corporation upgrades
function Upgrades({ corp, player, rerender }: IUpgradeProps): React.ReactElement { function Upgrades({ rerender }: IUpgradeProps): React.ReactElement {
const corp = useCorporation();
// Don't show upgrades // Don't show upgrades
if (corp.divisions.length <= 0) { if (corp.divisions.length <= 0) {
return <h1>Upgrades are unlocked once you create an industry.</h1>; return <Typography variant="h4">Upgrades are unlocked once you create an industry.</Typography>;
} }
return ( return (
@ -169,29 +156,32 @@ function Upgrades({ corp, player, rerender }: IUpgradeProps): React.ReactElement
{Object.values(CorporationUnlockUpgrades) {Object.values(CorporationUnlockUpgrades)
.filter((upgrade: CorporationUnlockUpgrade) => corp.unlockUpgrades[upgrade[0]] === 0) .filter((upgrade: CorporationUnlockUpgrade) => corp.unlockUpgrades[upgrade[0]] === 0)
.map((upgrade: CorporationUnlockUpgrade) => ( .map((upgrade: CorporationUnlockUpgrade) => (
<UnlockUpgrade rerender={rerender} player={player} corp={corp} upgradeData={upgrade} key={upgrade[0]} /> <UnlockUpgrade rerender={rerender} upgradeData={upgrade} key={upgrade[0]} />
))} ))}
<h1 className={"cmpy-mgmt-upgrade-header"}> Upgrades </h1> <h1 className={"cmpy-mgmt-upgrade-header"}> Upgrades </h1>
{corp.upgrades {corp.upgrades
.map((level: number, i: number) => CorporationUpgrades[i]) .map((level: number, i: number) => CorporationUpgrades[i])
.map((upgrade: CorporationUpgrade) => ( .map((upgrade: CorporationUpgrade) => (
<LevelableUpgrade rerender={rerender} player={player} corp={corp} upgrade={upgrade} key={upgrade[0]} /> <LevelableUpgrade rerender={rerender} upgrade={upgrade} key={upgrade[0]} />
))} ))}
</div> </div>
); );
} }
interface IPublicButtonsProps { interface IPublicButtonsProps {
corp: ICorporation;
player: IPlayer;
rerender: () => void; rerender: () => void;
} }
// Render the buttons for when your Corporation has gone public // Render the buttons for when your Corporation has gone public
function PublicButtons({ corp, player, rerender }: IPublicButtonsProps): React.ReactElement { function PublicButtons({ rerender }: IPublicButtonsProps): React.ReactElement {
const corp = useCorporation();
const [sellSharesOpen, setSellSharesOpen] = useState(false);
const [buybackSharesOpen, setBuybackSharesOpen] = useState(false);
const [issueNewSharesOpen, setIssueNewSharesOpen] = useState(false);
const [issueDividendsOpen, setIssueDividendsOpen] = useState(false);
const sellSharesOnCd = corp.shareSaleCooldown > 0; const sellSharesOnCd = corp.shareSaleCooldown > 0;
const sellSharesClass = sellSharesOnCd ? "a-link-button-inactive" : "std-button";
const sellSharesTooltip = sellSharesOnCd const sellSharesTooltip = sellSharesOnCd
? "Cannot sell shares for " + corp.convertCooldownToString(corp.shareSaleCooldown) ? "Cannot sell shares for " + corp.convertCooldownToString(corp.shareSaleCooldown)
: "Sell your shares in the company. The money earned from selling your " + : "Sell your shares in the company. The money earned from selling your " +
@ -199,139 +189,83 @@ function PublicButtons({ corp, player, rerender }: IPublicButtonsProps): React.R
"This is one of the only ways to profit from your business venture."; "This is one of the only ways to profit from your business venture.";
const issueNewSharesOnCd = corp.issueNewSharesCooldown > 0; const issueNewSharesOnCd = corp.issueNewSharesCooldown > 0;
const issueNewSharesClass = issueNewSharesOnCd ? "a-link-button-inactive" : "std-button";
const issueNewSharesTooltip = issueNewSharesOnCd const issueNewSharesTooltip = issueNewSharesOnCd
? "Cannot issue new shares for " + corp.convertCooldownToString(corp.issueNewSharesCooldown) ? "Cannot issue new shares for " + corp.convertCooldownToString(corp.issueNewSharesCooldown)
: "Issue new equity shares to raise capital."; : "Issue new equity shares to raise capital.";
function openSellSharesPopup(): void {
const popupId = "cmpy-mgmt-sell-shares-popup";
createPopup(popupId, SellSharesPopup, {
corp: corp,
player,
popupId,
rerender,
});
}
function openBuybackSharesPopup(): void {
const popupId = "corp-buyback-shares-popup";
createPopup(popupId, BuybackSharesPopup, {
rerender,
player,
popupId,
corp: corp,
});
}
function openIssueNewSharesPopup(): void {
const popupId = "cmpy-mgmt-issue-new-shares-popup";
createPopup(popupId, IssueNewSharesPopup, {
popupId,
corp: corp,
});
}
function openIssueDividendsPopup(): void {
const popupId = "cmpy-mgmt-issue-dividends-popup";
createPopup(popupId, IssueDividendsPopup, {
popupId,
corp: corp,
});
}
return ( return (
<> <>
<Button <Tooltip disableInteractive title={<Typography>{sellSharesTooltip}</Typography>}>
className={sellSharesClass} <Button disabled={sellSharesOnCd} onClick={() => setSellSharesOpen(true)}>
display="inline-block" Sell Shares
onClick={openSellSharesPopup} </Button>
text="Sell Shares" </Tooltip>
tooltip={sellSharesTooltip} <SellSharesModal open={sellSharesOpen} onClose={() => setSellSharesOpen(false)} rerender={rerender} />
/> <Tooltip
<Button disableInteractive
className="std-button" title={<Typography>Buy back shares you that previously issued or sold at market price.</Typography>}
display="inline-block" >
onClick={openBuybackSharesPopup} <Button onClick={() => setBuybackSharesOpen(true)}>Buyback shares</Button>
text="Buyback shares" </Tooltip>
tooltip="Buy back shares you that previously issued or sold at market price." <BuybackSharesModal open={buybackSharesOpen} onClose={() => setBuybackSharesOpen(false)} rerender={rerender} />
/>
<br /> <br />
<Button <Tooltip disableInteractive title={<Typography>{issueNewSharesTooltip}</Typography>}>
className={issueNewSharesClass} <Button disabled={issueNewSharesOnCd} onClick={() => setIssueNewSharesOpen(true)}>
display="inline-block" Issue New Shares
onClick={openIssueNewSharesPopup} </Button>
text="Issue New Shares" </Tooltip>
tooltip={issueNewSharesTooltip} <IssueNewSharesModal open={issueNewSharesOpen} onClose={() => setIssueNewSharesOpen(false)} />
/> <Tooltip
<Button disableInteractive
className="std-button" title={<Typography>Manage the dividends that are paid out to shareholders (including yourself)</Typography>}
display="inline-block" >
onClick={openIssueDividendsPopup} <Button onClick={() => setIssueDividendsOpen(true)}>Issue Dividends</Button>
text="Issue Dividends" </Tooltip>
tooltip="Manage the dividends that are paid out to shareholders (including yourself)" <IssueDividendsModal open={issueDividendsOpen} onClose={() => setIssueDividendsOpen(false)} />
/>
<br /> <br />
</> </>
); );
} }
// Generic Function for Creating a button function BribeButton(): React.ReactElement {
interface ICreateButtonProps { const player = use.Player();
text: string; const corp = useCorporation();
className?: string; const [open, setOpen] = useState(false);
display?: string; const canBribe =
tooltip?: string; corp.determineValuation() >= CorporationConstants.BribeThreshold &&
onClick?: (event: React.MouseEvent) => void; player.factions.filter((f) => Factions[f].getInfo().offersWork()).length > 0;
}
function Button({ className = "std-button", text, display, tooltip, onClick }: ICreateButtonProps): React.ReactElement { function openBribe(): void {
const hasTooltip = tooltip != null; if (!canBribe) return;
if (hasTooltip) className += " tooltip"; setOpen(true);
return (
<button className={className} onClick={onClick} style={{ display: display ? display : "block" }}>
{text}
{hasTooltip && <span className={"tooltiptext"}>{tooltip}</span>}
</button>
);
}
interface IBribeButtonProps {
player: IPlayer;
corp: ICorporation;
}
function BribeButton({ player, corp }: IBribeButtonProps): React.ReactElement {
function openBribeFactionPopup(): void {
const popupId = "corp-bribe-popup";
createPopup(popupId, BribeFactionPopup, {
player,
popupId,
corp: corp,
});
} }
const canBribe = corp.determineValuation() >= CorporationConstants.BribeThreshold || true;
const bribeFactionsClass = canBribe ? "a-link-button" : "a-link-button-inactive";
return ( return (
<Button <>
className={bribeFactionsClass} <Tooltip
display="inline-block" disableInteractive
onClick={openBribeFactionPopup} title={
text="Bribe Factions" canBribe
tooltip={ ? "Use your Corporations power and influence to bribe Faction leaders in exchange for reputation"
canBribe : "Your Corporation is not powerful enough to bribe Faction leaders"
? "Use your Corporations power and influence to bribe Faction leaders in exchange for reputation" }
: "Your Corporation is not powerful enough to bribe Faction leaders" >
} <span>
/> <Button disabled={!canBribe} onClick={openBribe}>
Bribe Factions
</Button>
</span>
</Tooltip>
<BribeFactionModal open={open} onClose={() => setOpen(false)} />
</>
); );
} }
interface IDividendsStatsProps { interface IDividendsStatsProps {
corp: ICorporation;
profit: number; profit: number;
} }
function DividendsStats({ corp, profit }: IDividendsStatsProps): React.ReactElement { function DividendsStats({ profit }: IDividendsStatsProps): React.ReactElement {
const corp = useCorporation();
if (corp.dividendPercentage <= 0 || profit <= 0) return <></>; if (corp.dividendPercentage <= 0 || profit <= 0) return <></>;
const totalDividends = (corp.dividendPercentage / 100) * profit; const totalDividends = (corp.dividendPercentage / 100) * profit;
const retainedEarnings = profit - totalDividends; const retainedEarnings = profit - totalDividends;
@ -361,26 +295,24 @@ interface IMultProps {
function Mult({ name, mult }: IMultProps): React.ReactElement { function Mult({ name, mult }: IMultProps): React.ReactElement {
if (mult <= 1) return <></>; if (mult <= 1) return <></>;
return ( return (
<p> <Typography>
{name} {name}
{numeralWrapper.format(mult, "0.000")} {numeralWrapper.format(mult, "0.000")}
<br /> <br />
</p> </Typography>
); );
} }
interface IBonusTimeProps {
corp: ICorporation;
}
// Returns a string with general information about Corporation // Returns a string with general information about Corporation
function BonusTime({ corp }: IBonusTimeProps): React.ReactElement { function BonusTime(): React.ReactElement {
const corp = useCorporation();
const storedTime = corp.storedCycles * CONSTANTS.MilliPerCycle; const storedTime = corp.storedCycles * CONSTANTS.MilliPerCycle;
if (storedTime <= 15000) return <></>; if (storedTime <= 15000) return <></>;
return ( return (
<p> <Typography>
Bonus time: {convertTimeMsToTimeElapsedString(storedTime)} Bonus time: {convertTimeMsToTimeElapsedString(storedTime)}
<br /> <br />
<br /> <br />
</p> </Typography>
); );
} }

@ -1,5 +1,5 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { dialogBoxCreate } from "../../../utils/DialogBox"; import { dialogBoxCreate } from "../../ui/React/DialogBox";
import { removePopup } from "../../ui/React/createPopup"; import { removePopup } from "../../ui/React/createPopup";
import { MaterialSizes } from "../MaterialSizes"; import { MaterialSizes } from "../MaterialSizes";
import { Warehouse } from "../Warehouse"; import { Warehouse } from "../Warehouse";

@ -1,5 +1,5 @@
import React, { useEffect } from "react"; import React, { useEffect } from "react";
import { dialogBoxCreate } from "../../../utils/DialogBox"; import { dialogBoxCreate } from "../../ui/React/DialogBox";
import { removePopup } from "../../ui/React/createPopup"; import { removePopup } from "../../ui/React/createPopup";
import { IndustryResearchTrees } from "../IndustryData"; import { IndustryResearchTrees } from "../IndustryData";
import { CorporationConstants } from "../data/Constants"; import { CorporationConstants } from "../data/Constants";

@ -1,5 +1,5 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { dialogBoxCreate } from "../../../utils/DialogBox"; import { dialogBoxCreate } from "../../ui/React/DialogBox";
import { removePopup } from "../../ui/React/createPopup"; import { removePopup } from "../../ui/React/createPopup";
import { ICorporation } from "../ICorporation"; import { ICorporation } from "../ICorporation";
import { Material } from "../Material"; import { Material } from "../Material";

@ -1,5 +1,5 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { dialogBoxCreate } from "../../../utils/DialogBox"; import { dialogBoxCreate } from "../../ui/React/DialogBox";
import { removePopup } from "../../ui/React/createPopup"; import { removePopup } from "../../ui/React/createPopup";
import { Product } from "../Product"; import { Product } from "../Product";
import { SellProduct } from "../Actions"; import { SellProduct } from "../Actions";

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