Merge pull request #1057 from danielyxie/dev

v0.52.4 - Bladeburner in React
This commit is contained in:
hydroflame 2021-08-19 01:46:16 -04:00 committed by GitHub
commit 5098ef6232
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
72 changed files with 3543 additions and 3553 deletions

File diff suppressed because one or more lines are too long

@ -1,2 +1,2 @@
!function(n){function t(t){for(var e,i,f=t[0],c=t[1],l=t[2],p=0,s=[];p<f.length;p++)i=f[p],u[i]&&s.push(u[i][0]),u[i]=0;for(e in c)Object.prototype.hasOwnProperty.call(c,e)&&(n[e]=c[e]);for(a&&a(t);s.length;)s.shift()();return r.push.apply(r,l||[]),o()}function o(){for(var n,t=0;t<r.length;t++){for(var o=r[t],e=!0,f=1;f<o.length;f++){var c=o[f];0!==u[c]&&(e=!1)}e&&(r.splice(t--,1),n=i(i.s=o[0]))}return n}var e={},u={1:0},r=[];function i(t){if(e[t])return e[t].exports;var o=e[t]={i:t,l:!1,exports:{}};return n[t].call(o.exports,o,o.exports,i),o.l=!0,o.exports}i.m=n,i.c=e,i.d=function(n,t,o){i.o(n,t)||Object.defineProperty(n,t,{enumerable:!0,get:o})},i.r=function(n){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(n,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(n,"__esModule",{value:!0})},i.t=function(n,t){if(1&t&&(n=i(n)),8&t)return n;if(4&t&&"object"==typeof n&&n&&n.__esModule)return n;var o=Object.create(null);if(i.r(o),Object.defineProperty(o,"default",{enumerable:!0,value:n}),2&t&&"string"!=typeof n)for(var e in n)i.d(o,e,function(t){return n[t]}.bind(null,e));return o},i.n=function(n){var t=n&&n.__esModule?function(){return n.default}:function(){return n};return i.d(t,"a",t),t},i.o=function(n,t){return Object.prototype.hasOwnProperty.call(n,t)},i.p="";var f=window.webpackJsonp=window.webpackJsonp||[],c=f.push.bind(f);f.push=t,f=f.slice();for(var l=0;l<f.length;l++)t(f[l]);var a=c;r.push([858,0]),o()}({795:function(n,t,o){},797:function(n,t,o){},799:function(n,t,o){},801:function(n,t,o){},803:function(n,t,o){},805:function(n,t,o){},807:function(n,t,o){},809:function(n,t,o){},811:function(n,t,o){},813:function(n,t,o){},815:function(n,t,o){},817:function(n,t,o){},819:function(n,t,o){},821:function(n,t,o){},823:function(n,t,o){},825:function(n,t,o){},827:function(n,t,o){},829:function(n,t,o){},831:function(n,t,o){},833:function(n,t,o){},835:function(n,t,o){},837:function(n,t,o){},839:function(n,t,o){},841:function(n,t,o){},843:function(n,t,o){},845:function(n,t,o){},847:function(n,t,o){},849:function(n,t,o){},851:function(n,t,o){},853:function(n,t,o){},855:function(n,t,o){},858:function(n,t,o){"use strict";o.r(t);o(857),o(855),o(853),o(851),o(849),o(847),o(845),o(843),o(841),o(839),o(837),o(835),o(833),o(831),o(829),o(827),o(825),o(823),o(821),o(819),o(817),o(815),o(813),o(811),o(809),o(807),o(805),o(803),o(801),o(799),o(797),o(795)}});
!function(n){function t(t){for(var e,i,f=t[0],c=t[1],l=t[2],p=0,s=[];p<f.length;p++)i=f[p],u[i]&&s.push(u[i][0]),u[i]=0;for(e in c)Object.prototype.hasOwnProperty.call(c,e)&&(n[e]=c[e]);for(a&&a(t);s.length;)s.shift()();return r.push.apply(r,l||[]),o()}function o(){for(var n,t=0;t<r.length;t++){for(var o=r[t],e=!0,f=1;f<o.length;f++){var c=o[f];0!==u[c]&&(e=!1)}e&&(r.splice(t--,1),n=i(i.s=o[0]))}return n}var e={},u={1:0},r=[];function i(t){if(e[t])return e[t].exports;var o=e[t]={i:t,l:!1,exports:{}};return n[t].call(o.exports,o,o.exports,i),o.l=!0,o.exports}i.m=n,i.c=e,i.d=function(n,t,o){i.o(n,t)||Object.defineProperty(n,t,{enumerable:!0,get:o})},i.r=function(n){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(n,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(n,"__esModule",{value:!0})},i.t=function(n,t){if(1&t&&(n=i(n)),8&t)return n;if(4&t&&"object"==typeof n&&n&&n.__esModule)return n;var o=Object.create(null);if(i.r(o),Object.defineProperty(o,"default",{enumerable:!0,value:n}),2&t&&"string"!=typeof n)for(var e in n)i.d(o,e,function(t){return n[t]}.bind(null,e));return o},i.n=function(n){var t=n&&n.__esModule?function(){return n.default}:function(){return n};return i.d(t,"a",t),t},i.o=function(n,t){return Object.prototype.hasOwnProperty.call(n,t)},i.p="";var f=window.webpackJsonp=window.webpackJsonp||[],c=f.push.bind(f);f.push=t,f=f.slice();for(var l=0;l<f.length;l++)t(f[l]);var a=c;r.push([856,0]),o()}({793:function(n,t,o){},795:function(n,t,o){},797:function(n,t,o){},799:function(n,t,o){},801:function(n,t,o){},803:function(n,t,o){},805:function(n,t,o){},807:function(n,t,o){},809:function(n,t,o){},811:function(n,t,o){},813:function(n,t,o){},815:function(n,t,o){},817:function(n,t,o){},819:function(n,t,o){},821:function(n,t,o){},823:function(n,t,o){},825:function(n,t,o){},827:function(n,t,o){},829:function(n,t,o){},831:function(n,t,o){},833:function(n,t,o){},835:function(n,t,o){},837:function(n,t,o){},839:function(n,t,o){},841:function(n,t,o){},843:function(n,t,o){},845:function(n,t,o){},847:function(n,t,o){},849:function(n,t,o){},851:function(n,t,o){},853:function(n,t,o){},856:function(n,t,o){"use strict";o.r(t);o(855),o(853),o(851),o(849),o(847),o(845),o(843),o(841),o(839),o(837),o(835),o(833),o(831),o(829),o(827),o(825),o(823),o(821),o(819),o(817),o(815),o(813),o(811),o(809),o(807),o(805),o(803),o(801),o(799),o(797),o(795),o(793)}});
//# sourceMappingURL=engineStyle.bundle.js.map

34
dist/vendor.bundle.js vendored

File diff suppressed because one or more lines are too long

@ -3,6 +3,28 @@
Changelog
=========
v0.52.4 - 2021-07-19 Bladeburner in React (hydroflame)
-------------------------------------------
** Bladeburner **
* The entire UI was rebuild in React. It should be more responsive
** Hacknet **
* Displays how many time each hash upgrade was bought.
* Displays cummulative effect of the upgrade.
* Removed "Close" button from hash upgrade menu.
** Misc. **
* More popup/modals have dark background, can be dismissed by clicking
outside, or by pressing escape.
* Small reword in the guide.
* Fix several typos in the bladeburner documentation.
* Linting (no one cares except the dev)
* nerf noodle bar
v0.52.3 - 2021-07-15 Gangs were OP (hydroflame)
-------------------------------------------
@ -24,6 +46,7 @@ v0.52.3 - 2021-07-15 Gangs were OP (hydroflame)
* Fixed an issue where you could join the same faction twice via script and
UI simultaneously.
* Factions list screen converted to React.
* nerf noodle bar
v0.52.2 - 2021-07-15 Oh yeah, BN11 is a thing (drunk hydroflame tbh)
-------------------------------------------
@ -39,6 +62,10 @@ v0.52.2 - 2021-07-15 Oh yeah, BN11 is a thing (drunk hydroflame tbh)
programs.
* Augmentation descriptions are now more concise and consistent.
** Misc. **
* nerf noodle bar
v0.52.1 - 2021-07-10 bugfixing (hydroflame & community)
-------------------------------------------
@ -58,6 +85,7 @@ v0.52.1 - 2021-07-10 bugfixing (hydroflame & community)
* Plenty of typo/description fixed (@MageKing17)
* Cleanup description of singularity function on readthedocs (@PurePandemonium)
* Fix bug when autolinking a server while backdooring (@schroederIT)
* nerf noodle bar
v0.52.0 - 2021-06-13 Infiltration 2.0 (hydroflame & community)
--------------------------------------------------------------
@ -107,6 +135,7 @@ v0.52.0 - 2021-06-13 Infiltration 2.0 (hydroflame & community)
* Fixed an issue where reputation could be transfered to new jobs when unfocused.
* Empty stack traces should no longer appear.
* Purchasing anything with Infinity money doesn't result in NaN.
* nerf noodle bar
v0.51.10 - 2021-05-31 Focus Mark, Focus! (hydroflame)
-----------------------------------------------------
@ -150,6 +179,7 @@ v0.51.10 - 2021-05-31 Focus Mark, Focus! (hydroflame)
* Very large number will no longer appear as "$NaNt"
* Hash capacity now displays in the "big number" format.
* nerf noodle bar
v0.51.9 - 2021-05-17 offline progress and exports! (hydroflame & community)
---------------------------------------------------------------
@ -203,6 +233,7 @@ v0.51.9 - 2021-05-17 offline progress and exports! (hydroflame & community)
* Updated several dependencies (big who cares, I know)
* ls no longer prints lingering newline.
* Money earned/spent by sleeves is now tracked under Character>Money
* nerf noodle bar
v0.51.8 - 2021-05-07 It was there all along (hydroflame & community)
@ -260,6 +291,7 @@ v0.51.8 - 2021-05-07 It was there all along (hydroflame & community)
* Fix infiltration number formatting.
* script income transfers to parent on death. This helps keep track of
income for scripts that spawn short lived scripts.
* nerf noodle bar
v0.51.7 - 2021-04-28 n00dles (hydroflame & community)
-----------------------------------------
@ -317,6 +349,7 @@ v0.51.7 - 2021-04-28 n00dles (hydroflame & community)
* Money amount under 1000 dont display 3 decimal anymore.
* Fix nextSourceFile flag miscalculation on the bitverse (for Bn12)
* Faction invite text says "Decide later"/"Join!" instead of "No"/"Yes"
* nerf noodle bar
v0.51.6 - 2021-04-28 Backdoor! (hydroflame & community)
@ -367,6 +400,7 @@ v0.51.6 - 2021-04-28 Backdoor! (hydroflame & community)
* so many documentation and typos fixes (@Pimgd)
* A corruption visual effect has been added to location with servers that
have backdoor installed. (@dewint)
* nerf noodle bar
v0.51.5 - 2021-04-20 Flags! (hydroflame)
@ -391,6 +425,7 @@ v0.51.5 - 2021-04-20 Flags! (hydroflame)
* Souce-File typo fix
* Fix 'while you were away' screen.
* Bladeburner team size can no longer be set to negative amounts.
* nerf noodle bar
v0.51.4 - 2021-04-19 Manual hacking is fun (hydroflame)
-------------------------------------------------------
@ -426,6 +461,7 @@ v0.51.4 - 2021-04-19 Manual hacking is fun (hydroflame)
* The text editor now remembers the location of your cursor and restores it.
* skills are recalculated instantly.
* Fix typo in Operation Zero description.
* nerf noodle bar
v0.51.3 - 2021-04-16 Y'all broke it on the first day (hydroflame)
-----------------------------------------------------------------
@ -467,6 +503,7 @@ v0.51.3 - 2021-04-16 Y'all broke it on the first day (hydroflame)
* Hacknet node names is easier to handle for screen readers.
* Money spent on classes is now tracked independently of work money.
* running coding contract from the terminal will display its name.
* nerf noodle bar
v0.51.2 - 2021-04-09 Vegas, Baby! (hydroflame)
----------------------------------------------
@ -481,6 +518,7 @@ v0.51.2 - 2021-04-09 Vegas, Baby! (hydroflame)
* Link to discord added under options
* 'getMemberInformation' doc updated, oops
* tech vendor now handle max ram and cores.
* nerf noodle bar
v0.51.1 - 2021-04-06 Bugfixes because the author of the last patch sucks (it's hydroflame)
------------------------------------------------------------------------------------------
@ -512,6 +550,7 @@ v0.51.1 - 2021-04-06 Bugfixes because the author of the last patch sucks (it's h
* 'fl1ght.exe' will no longer suggest the combat path. Related faction
requirements unchanged.
* nerf noodle bar
v0.51.0 - 2021-03-31 Formulas (hydroflame)
------------------------------------------
@ -538,6 +577,10 @@ v0.51.0 - 2021-03-31 Formulas (hydroflame)
* Certain UI elements are now 'click-to-copy'
** Misc. **
* nerf noodle bar
v0.50.2 - 2021-03-25 Everyone asked for this one. (hydroflame)
--------------------------------------------------------------
@ -555,6 +598,7 @@ v0.50.2 - 2021-03-25 Everyone asked for this one. (hydroflame)
* New shortcut, Alt + b, brings you to bladeburner
* New shortcut, Alt + g, brings you to gang
* nerf noodle bar
v0.50.1 - 2021-03-22 (hydroflame)
---------------------------------
@ -577,6 +621,7 @@ v0.50.1 - 2021-03-22 (hydroflame)
**Misc.**
* Minor spacing in stats tables.
* nerf noodle bar
v0.50.0 - 2021-03-20 Intelligence (hydroflame)
----------------------------------------------
@ -597,6 +642,7 @@ v0.50.0 - 2021-03-20 Intelligence (hydroflame)
* number formatting
* remove wiki button in Hacking Missions.
* Fix NaN displayed when very very large numbers are reached.
* nerf noodle bar
v0.49.2 - 2021-03-13 (hydroflame)
---------------------------------
@ -634,6 +680,7 @@ v0.49.2 - 2021-03-13 (hydroflame)
**Misc.**
* Fix issue where the effective stats under Character>Stats were being calculated.
* nerf noodle bar
v0.49.0 - 2021-03-11 Source-File -1 (hydroflame)
------------------------------------------------
@ -661,6 +708,7 @@ v0.49.0 - 2021-03-11 Source-File -1 (hydroflame)
* Minor formatting under Hacking>Active Scripts
* option menu colors now match the rest of the game, kinda.
* nerf noodle bar
v0.48.0 - ASCII - 2021-03-07 (hydroflame)
@ -708,6 +756,7 @@ v0.48.0 - ASCII - 2021-03-07 (hydroflame)
has bought but not installed
* Character>Factions has a badge indicating how many factions have pending
invites.
* nerf noodle bar
v0.47.2 - 7/15/2019
-------------------

@ -66,7 +66,7 @@ documentation_title = '{0} Documentation'.format(project)
# The short X.Y version.
version = '0.52'
# The full version, including alpha/beta/rc tags.
release = '0.52.3'
release = '0.52.4'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.

@ -636,7 +636,7 @@ This tells me that I can reach :code:`CSEC` by going through :code:`iron-gym`::
from CSEC once you hit 50 hacking, you cannot actually pass their test
until your hacking is high enough to install a backdoor on their server.
After you are connected to the :code:`CSEC` server, you can hack it. Note that this
After you are connected to the :code:`CSEC` server, you can backdoor it. Note that this
server requires one open port in order to gain root access. We can open the SSH port
using the :code:`BruteSSH.exe` program we created earlier. In |Terminal|::

@ -12,7 +12,7 @@ getActionCountRemaining() Netscript Function
This function will return 'Infinity' for actions such as 'Training' and
'Field Analysis'.
This function will return 1 for BlackOps not yet completed regardless of
wether the player has the required rank to attempt the mission or not.
whether the player has the required rank to attempt the mission or not.
Example:

@ -12,4 +12,4 @@ getActionCurrentLevel() Netscript Function
.. code-block:: javascript
bladeburner.getActionCountRemaining("Contracts", "Tracking"); // returns: 7
bladeburner.getActionCurrentLevel("Contracts", "Tracking"); // returns: 9

@ -230,8 +230,10 @@
<div id="augmentations-container" class="generic-menupage-container"></div>
<!-- Milestones content -->
<div id="milestones-container" class="generic-menupage-container">
</div>
<div id="milestones-container" class="generic-menupage-container"></div>
<!-- Bladeburner -->
<div id="bladeburner-container" class="generic-menupage-container"></div>
<!-- Tutorial content -->
<div id="tutorial-container" class="generic-menupage-container">

@ -126,5 +126,5 @@
"watch": "webpack --watch --mode production",
"watch:dev": "webpack --watch --mode development"
},
"version": "0.52.3"
"version": "0.52.4"
}

@ -55,7 +55,7 @@ interface IConstructorParams {
}
function generateStatsDescription(mults: IMap<number>, programs?: string[], startingMoney?: number): JSX.Element {
const f = (x: number, decimals: number = 0) => {
const f = (x: number, decimals = 0): string => {
// look, I don't know how to make a "smart decimals"
// todo, make it smarter
if(x === 1.0777-1) return "7.77%";

@ -18,8 +18,6 @@ import { SourceFileFlags } from "../SourceFile/SourceFileFlags";
import { dialogBoxCreate } from "../../utils/DialogBox";
import { clearObject } from "../../utils/helpers/clearObject";
import { Money } from "../ui/React/Money";
import { CorruptableText } from "../ui/React/CorruptableText";
import { WHRNG } from "../Casino/RNG";

File diff suppressed because it is too large Load Diff

@ -0,0 +1,28 @@
import { IActionIdentifier } from "./IActionIdentifier";
import { Generic_fromJSON, Generic_toJSON, Reviver } from "../../utils/JSONReviver";
interface IParams {
name?: string;
type?: number;
}
export class ActionIdentifier implements IActionIdentifier {
name = "";
type = -1;
constructor(params: IParams = {}) {
if (params.name) this.name = params.name;
if (params.type) this.type = params.type;
}
toJSON(): any {
return Generic_toJSON("ActionIdentifier", this);
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
static fromJSON(value: any): ActionIdentifier {
return Generic_fromJSON(ActionIdentifier, value.data);
}
}
Reviver.constructors.ActionIdentifier = ActionIdentifier;

File diff suppressed because it is too large Load Diff

@ -4,14 +4,14 @@ import { getRandomInt } from "../../utils/helpers/getRandomInt";
import { Generic_fromJSON, Generic_toJSON, Reviver } from "../../utils/JSONReviver";
import { addOffset } from "../../utils/helpers/addOffset";
export class ChangePopulationByCountParams {
estChange = 0;
estOffset = 0;
interface IChangePopulationByCountParams {
estChange: number;
estOffset: number;
}
export class ChangePopulationByPercentageParams {
nonZero = false;
changeEstEqually = false;
interface IChangePopulationByPercentageParams {
nonZero: boolean;
changeEstEqually: boolean;
}
export class City {
@ -113,7 +113,7 @@ export class City {
* estChange(int): How much the estimate should change by
* estOffset(int): Add offset to estimate (offset by percentage)
*/
changePopulationByCount(n: number, params: ChangePopulationByCountParams=new ChangePopulationByCountParams()): void {
changePopulationByCount(n: number, params: IChangePopulationByCountParams = {estChange: 0, estOffset: 0}): void {
if (isNaN(n)) {throw new Error("NaN passed into City.changePopulationByCount()");}
this.pop += n;
if (params.estChange && !isNaN(params.estChange)) {this.popEst += params.estChange;}
@ -129,7 +129,7 @@ export class City {
* changeEstEqually(bool) - Change the population estimate by an equal amount
* nonZero (bool) - Set to true to ensure that population always changes by at least 1
*/
changePopulationByPercentage(p: number, params: ChangePopulationByPercentageParams=new ChangePopulationByPercentageParams()): number {
changePopulationByPercentage(p: number, params: IChangePopulationByPercentageParams={nonZero: false, changeEstEqually: false}): number {
if (isNaN(p)) {throw new Error("NaN passed into City.changePopulationByPercentage()");}
if (p === 0) {return 0;}
let change = Math.round(this.pop * (p/100));

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

@ -1,20 +1,33 @@
import { IActionIdentifier } from "./IActionIdentifier";
import { City } from "./City";
import { Skill } from "./Skill";
import { IAction } from "./IAction";
import { IPlayer } from "../PersonObjects/IPlayer";
import { WorkerScript } from "../Netscript/WorkerScript";
export interface IBladeburner {
numHosp: number;
moneyLost: number;
rank: number;
maxRank: number;
skillPoints: number;
totalSkillPoints: number;
teamSize: number;
teamLost: number;
hpLost: number;
storedCycles: number;
randomEventCounter: number;
actionTimeToComplete: number;
actionTimeCurrent: number;
actionTimeOverflow: number;
action: IActionIdentifier;
cities: any;
city: string;
skills: any;
@ -27,13 +40,66 @@ export interface IBladeburner {
blackops: any;
logging: any;
automateEnabled: boolean;
automateActionHigh: number;
automateActionHigh: IActionIdentifier;
automateThreshHigh: number;
automateActionLow: number;
automateActionLow: IActionIdentifier;
automateThreshLow: number;
consoleHistory: string[];
consoleLogs: string[];
getCurrentCity(): City;
calculateStaminaPenalty(): number;
startAction(player: IPlayer, action: IActionIdentifier): void;
upgradeSkill(skill: Skill): void;
executeConsoleCommands(player: IPlayer, command: string): void;
postToConsole(input: string, saveToLogs?: boolean): void;
log(input: string): void;
resetAction(): void;
clearConsole(): void;
prestige(): void;
storeCycles(numCycles?: number): void;
getTypeAndNameFromActionId(actionId: IActionIdentifier): {type: string; name: string};
getContractNamesNetscriptFn(): string[];
getOperationNamesNetscriptFn(): string[];
getBlackOpNamesNetscriptFn(): string[];
getGeneralActionNamesNetscriptFn(): string[];
getSkillNamesNetscriptFn(): string[];
startActionNetscriptFn(player: IPlayer, type: string, name: string, workerScript: WorkerScript): boolean;
getActionTimeNetscriptFn(player: IPlayer, type: string, name: string, workerScript: WorkerScript): number;
getActionEstimatedSuccessChanceNetscriptFn(player: IPlayer, type: string, name: string, workerScript: WorkerScript): number;
getActionCountRemainingNetscriptFn(type: string, name: string, workerScript: WorkerScript): number;
getSkillLevelNetscriptFn(skillName: string, workerScript: WorkerScript): number;
getSkillUpgradeCostNetscriptFn(skillName: string, workerScript: WorkerScript): number;
upgradeSkillNetscriptFn(skillName: string, workerScript: WorkerScript): boolean;
getTeamSizeNetscriptFn(type: string, name: string, workerScript: WorkerScript): number;
setTeamSizeNetscriptFn(type: string, name: string, size: number, workerScript: WorkerScript): number;
joinBladeburnerFactionNetscriptFn(workerScript: WorkerScript): boolean;
getActionIdFromTypeAndName(type: string, name: string): IActionIdentifier | null;
executeStartConsoleCommand(player: IPlayer, args: string[]): void;
executeSkillConsoleCommand(args: string[]): void;
executeLogConsoleCommand(args: string[]): void;
executeHelpConsoleCommand(args: string[]): void;
executeAutomateConsoleCommand(args: string[]): void;
parseCommandArguments(command: string): string[];
executeConsoleCommand(player: IPlayer, command: string): void;
triggerMigration(sourceCityName: string): void;
triggerPotentialMigration(sourceCityName: string, chance: number): void;
randomEvent(): void;
gainActionStats(player: IPlayer, action: IAction, success: boolean): void;
getDiplomacyEffectiveness(player: IPlayer): number;
getRecruitmentSuccessChance(player: IPlayer): number;
getRecruitmentTime(player: IPlayer): number;
resetSkillMultipliers(): void;
updateSkillMultipliers(): void;
completeOperation(success: boolean): void;
getActionObject(actionId: IActionIdentifier): IAction | null;
completeContract(success: boolean): void;
completeAction(player: IPlayer): void;
changeRank(player: IPlayer, change: number): void;
processAction(player: IPlayer, seconds: number): void;
calculateStaminaGainPerSecond(player: IPlayer): number;
calculateMaxStamina(player: IPlayer): void;
create(): void;
process(player: IPlayer): void;
}

@ -103,5 +103,28 @@ export class Skill {
calculateCost(currentLevel: number): number {
return Math.floor((this.baseCost + (currentLevel * this.costInc)) * BitNodeMultipliers.BladeburnerSkillCost);
}
getMultiplier(name: string): number {
if(name === "successChanceAll") return this.successChanceAll;
if(name === "successChanceStealth") return this.successChanceStealth;
if(name === "successChanceKill") return this.successChanceKill;
if(name === "successChanceContract") return this.successChanceContract;
if(name === "successChanceOperation") return this.successChanceOperation;
if(name === "successChanceEstimate") return this.successChanceEstimate;
if(name === "actionTime") return this.actionTime;
if(name === "effHack") return this.effHack;
if(name === "effStr") return this.effStr;
if(name === "effDef") return this.effDef;
if(name === "effDex") return this.effDex;
if(name === "effAgi") return this.effAgi;
if(name === "effCha") return this.effCha;
if(name === "stamina") return this.stamina;
if(name === "money") return this.money;
if(name === "expGain") return this.expGain;
return 0;
}
}

@ -1,5 +1,18 @@
// Action Identifier enum
export const ActionTypes = Object.freeze({
export const ActionTypes: {
[key: string]: number;
"Idle": number;
"Contract": number;
"Operation": number;
"BlackOp": number;
"BlackOperation": number;
"Training": number;
"Recruitment": number;
"FieldAnalysis": number;
"Field Analysis": number;
"Diplomacy": number;
"Hyperbolic Regeneration Chamber": number;
} = {
"Idle": 1,
"Contract": 2,
"Operation": 3,
@ -11,4 +24,4 @@ export const ActionTypes = Object.freeze({
"Field Analysis": 7,
"Diplomacy": 8,
"Hyperbolic Regeneration Chamber": 9,
});
};

@ -1,4 +1,5 @@
export const ConsoleHelpText: {
[key: string]: string[];
helpList: string[];
automate: string[];
clear: string[];

@ -0,0 +1,14 @@
import * as React from "react";
export const stealthIcon = <svg xmlns="http://www.w3.org/2000/svg" width="16px" height="16px" viewBox="0 0 166 132" style={{fill:'#adff2f'}}>
<g>
<path d="M132.658-0.18l-24.321,24.321c-7.915-2.71-16.342-4.392-25.087-4.392c-45.84,0-83,46-83,46 s14.1,17.44,35.635,30.844L12.32,120.158l12.021,12.021L144.68,11.841L132.658-0.18z M52.033,80.445 c-2.104-4.458-3.283-9.438-3.283-14.695c0-19.054,15.446-34.5,34.5-34.5c5.258,0,10.237,1.179,14.695,3.284L52.033,80.445z" />
<path d="M134.865,37.656l-18.482,18.482c0.884,3.052,1.367,6.275,1.367,9.612c0,19.055-15.446,34.5-34.5,34.5 c-3.337,0-6.56-0.483-9.611-1.367l-10.124,10.124c6.326,1.725,12.934,2.743,19.735,2.743c45.84,0,83-46,83-46 S153.987,50.575,134.865,37.656z" />
</g>
</svg>
export const killIcon = <svg xmlns="http://www.w3.org/2000/svg" width="16px" height="16px" viewBox="-22 0 511 511.99561" style={{fill:'#adff2f'}}>
<path d="m.496094 466.242188 39.902344-39.902344 45.753906 45.753906-39.898438 39.902344zm0 0" />
<path d="m468.421875 89.832031-1.675781-89.832031-300.265625 300.265625 45.753906 45.753906zm0 0" />
<path d="m95.210938 316.785156 16.84375 16.847656h.003906l83.65625 83.65625 22.753906-22.753906-100.503906-100.503906zm0 0" />
<path d="m101.445312 365.300781-39.902343 39.902344 45.753906 45.753906 39.902344-39.902343-39.90625-39.902344zm0 0" />
</svg>

@ -0,0 +1,49 @@
import React, { useState, useEffect } from "react";
import { GeneralActionPage } from "./GeneralActionPage";
import { ContractPage } from "./ContractPage";
import { OperationPage } from "./OperationPage";
import { BlackOpPage } from "./BlackOpPage";
import { SkillPage } from "./SkillPage";
import { stealthIcon, killIcon } from "../data/Icons";
import { IBladeburner } from "../IBladeburner";
import { IPlayer } from "../../PersonObjects/IPlayer";
interface IProps {
bladeburner: IBladeburner;
player: IPlayer;
}
export function AllPages(props: IProps): React.ReactElement {
const [page, setPage] = useState('General');
const setRerender = useState(false)[1];
useEffect(() => {
const id = setInterval(() => setRerender(old => !old), 1000);
return () => clearInterval(id);
}, []);
function Header(props: {name: string}): React.ReactElement {
return (<a
onClick={()=>setPage(props.name)}
className={page !== props.name ?
"bladeburner-nav-button" :
"bladeburner-nav-button-inactive"}>
{props.name}
</a>);
}
return (<>
<Header name={'General'} />
<Header name={'Contracts'} />
<Header name={'Operations'} />
<Header name={'BlackOps'} />
<Header name={'Skills'} />
<div style={{display:"block", margin:"4px", padding:"4px"}}>
{page === 'General' && <GeneralActionPage bladeburner={props.bladeburner} player={props.player} />}
{page === 'Contracts' && <ContractPage bladeburner={props.bladeburner} player={props.player} />}
{page === 'Operations' && <OperationPage bladeburner={props.bladeburner} player={props.player} />}
{page === 'BlackOps' && <BlackOpPage bladeburner={props.bladeburner} player={props.player} />}
{page === 'Skills' && <SkillPage bladeburner={props.bladeburner} />}
</div>
<span className="text">{stealthIcon}= This action requires stealth, {killIcon} = This action involves retirement</span>
</>);
}

@ -0,0 +1,87 @@
import React, { useState } from "react";
import {
formatNumber,
convertTimeMsToTimeElapsedString,
} from "../../../utils/StringHelperFunctions";
import { ActionTypes } from "../data/ActionTypes";
import { createProgressBarText } from "../../../utils/helpers/createProgressBarText";
import { stealthIcon, killIcon } from "../data/Icons";
import { createPopup } from "../../ui/React/createPopup";
import { TeamSizePopup } from "./TeamSizePopup";
import { IBladeburner } from "../IBladeburner";
import { IPlayer } from "../../PersonObjects/IPlayer";
interface IProps {
bladeburner: IBladeburner;
player: IPlayer;
action: any;
}
export function BlackOpElem(props: IProps): React.ReactElement {
const setRerender = useState(false)[1];
const isCompleted = (props.bladeburner.blackops[props.action.name] != null);
if(isCompleted) {
return (
<h2 style={{display: 'block'}}>{props.action.name} (COMPLETED)</h2>);
}
const isActive = props.bladeburner.action.type === ActionTypes["BlackOperation"] && props.action.name === props.bladeburner.action.name;
const estimatedSuccessChance = props.action.getSuccessChance(props.bladeburner, {est:true});
const actionTime = props.action.getActionTime(props.bladeburner);
const hasReqdRank = props.bladeburner.rank >= props.action.reqdRank;
const computedActionTimeCurrent = Math.min(props.bladeburner.actionTimeCurrent+props.bladeburner.actionTimeOverflow, props.bladeburner.actionTimeToComplete);
function onStart(): void {
props.bladeburner.action.type = ActionTypes.BlackOperation;
props.bladeburner.action.name = 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 (<>
<h2 style={{display: 'inline-block'}}>
{isActive ?
<>{props.action.name} (IN PROGRESS - {formatNumber(computedActionTimeCurrent, 0)} / {formatNumber(props.bladeburner.actionTimeToComplete, 0)})</> :
<>{props.action.name}</>
}
</h2>
{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 />
<p style={{display:"inline-block"}} dangerouslySetInnerHTML={{__html: props.action.desc}} />
<br />
<br />
<p style={{display:"block", color:hasReqdRank ? "white" : "red"}}>
Required Rank: {formatNumber(props.action.reqdRank, 0)}
</p>
<br />
<p style={{display:"inline-block"}}>
Estimated Success Chance: {formatNumber(estimatedSuccessChance*100, 1)}% {props.action.isStealth?stealthIcon:<></>}{props.action.isKill?killIcon:<></>}
<br />
Time Required: {convertTimeMsToTimeElapsedString(actionTime*1000)}
</p>
</>);
}

@ -0,0 +1,36 @@
import React from "react";
import { BlackOperations } from "../BlackOperations";
import { BlackOperation } from "../BlackOperation";
import { BlackOpElem } from "./BlackOpElem";
import { IBladeburner } from "../IBladeburner";
import { IPlayer } from "../../PersonObjects/IPlayer";
interface IProps {
bladeburner: IBladeburner;
player: IPlayer;
}
export function BlackOpList(props: IProps): React.ReactElement {
let blackops: BlackOperation[] = [];
for (const blackopName in BlackOperations) {
if (BlackOperations.hasOwnProperty(blackopName)) {
blackops.push(BlackOperations[blackopName]);
}
}
blackops.sort(function(a, b) {
return (a.reqdRank - b.reqdRank);
});
blackops = blackops.filter((blackop: BlackOperation, i: number) => !(props.bladeburner.blackops[blackops[i].name] == null &&
i !== 0 &&
props.bladeburner.blackops[blackops[i-1].name] == null));
blackops = blackops.reverse();
return (<>
{blackops.map((blackop: BlackOperation) => <li key={blackop.name} className="bladeburner-action">
<BlackOpElem bladeburner={props.bladeburner} action={blackop} player={props.player} />
</li>,
)}
</>);
}

@ -0,0 +1,28 @@
import * as React from "react";
import { BlackOpList } from "./BlackOpList";
import { IBladeburner } from "../IBladeburner";
import { IPlayer } from "../../PersonObjects/IPlayer";
interface IProps {
bladeburner: IBladeburner;
player: IPlayer;
}
export function BlackOpPage(props: IProps): React.ReactElement {
return (<>
<p style={{display: 'block', margin: '4px', padding: '4px'}}>
Black Operations (Black Ops) are special, one-time covert operations.
Each Black Op must be unlocked successively by completing
the one before it.
<br />
<br />
<b>Your ultimate goal to climb through the ranks of Bladeburners is to complete
all of the Black Ops.</b>
<br />
<br />
Like normal operations, you may use a team for Black Ops. Failing
a black op will incur heavy HP and rank losses.
</p>
<BlackOpList bladeburner={props.bladeburner} player={props.player} />
</>);
}

@ -1,62 +0,0 @@
import { BlackOperations } from "../BlackOperations";
/*
if (DomElems.actionsAndSkillsList == null || DomElems.actionsAndSkillsDesc == null) {
throw new Error("Bladeburner.createBlackOpsContent called with either " +
"DomElems.actionsAndSkillsList or DomElems.actionsAndSkillsDesc = null");
}
DomElems.actionsAndSkillsDesc.innerHTML =
"Black Operations (Black Ops) are special, one-time covert operations. " +
"Each Black Op must be unlocked successively by completing " +
"the one before it.<br><br>" +
"<b>Your ultimate goal to climb through the ranks of Bladeburners is to complete " +
"all of the Black Ops.</b><br><br>" +
"Like normal operations, you may use a team for Black Ops. Failing " +
"a black op will incur heavy HP and rank losses.";
// Put Black Operations in sequence of required rank
var blackops = [];
for (var blackopName in BlackOperations) {
if (BlackOperations.hasOwnProperty(blackopName)) {
blackops.push(BlackOperations[blackopName]);
}
}
blackops.sort(function(a, b) {
return (a.reqdRank - b.reqdRank);
});
for (var i = blackops.length-1; i >= 0 ; --i) {
if (this.blackops[[blackops[i].name]] == null && i !== 0 && this.blackops[[blackops[i-1].name]] == null) {continue;} // If this one nor the next are completed then this isn't unlocked yet.
DomElems.blackops[blackops[i].name] = createElement("div", {
class:"bladeburner-action", name:blackops[i].name
});
DomElems.actionsAndSkillsList.appendChild(DomElems.blackops[blackops[i].name]);
}
*/
import * as React from "react";
export function BlackOperationsPage(): React.ReactElement {
// Put Black Operations in sequence of required rank
const blackops = [];
for (const name in BlackOperations) {
if (BlackOperations.hasOwnProperty(name)) {
blackops.push(BlackOperations[name]);
}
}
blackops.sort(function(a, b) {
return (a.reqdRank - b.reqdRank);
});
return (<div>
<p>
Black Operations (Black Ops) are special, one-time covert operations. Each Black Op must be unlocked successively by completing the one before it.<br /><br />
<b>Your ultimate goal to climb through the ranks of Bladeburners is to complete all of the Black Ops.</b><br /><br />
Like normal operations, you may use a team for Black Ops. Failing a black op will incur heavy HP and rank losses.</p>
{blackops.map(() => <div className="bladeburner-action">
</div>,
)}
</div>)
}

@ -0,0 +1,115 @@
import React, { useState, useRef, useEffect } from "react";
import { IBladeburner } from "../IBladeburner";
import { IPlayer } from "../../PersonObjects/IPlayer";
interface ILineProps {
content: any;
}
function Line(props: ILineProps): React.ReactElement {
return (<tr>
<td className="bladeburner-console-line" style={{color: 'var(--my-font-color)', whiteSpace: 'pre-wrap'}}>{props.content}</td>
</tr>)
}
interface IProps {
bladeburner: IBladeburner;
player: IPlayer;
}
export function Console(props: IProps): React.ReactElement {
const lastRef = useRef<HTMLDivElement>(null);
const setRerender = useState(false)[1];
const [consoleHistoryIndex, setConsoleHistoryIndex] = useState(props.bladeburner.consoleHistory.length);
// TODO: Figure out how to actually make the scrolling work correctly.
function scrollToBottom(): void {
if(lastRef.current)
lastRef.current.scrollTop = lastRef.current.scrollHeight;
}
function rerender(): void {
setRerender(old => !old);
}
useEffect(() => {
const id = setInterval(rerender, 1000);
const id2 = setInterval(scrollToBottom, 100);
return () => {
clearInterval(id);
clearInterval(id2);
};
}, []);
function handleKeyDown(event: React.KeyboardEvent<HTMLInputElement>): void {
if (event.keyCode === 13) {
event.preventDefault();
const command = event.currentTarget.value;
event.currentTarget.value = "";
if (command.length > 0) {
props.bladeburner.postToConsole("> " + command);
props.bladeburner.executeConsoleCommands(props.player, command);
setConsoleHistoryIndex(props.bladeburner.consoleHistory.length);
rerender();
}
}
const consoleHistory = props.bladeburner.consoleHistory;
if (event.keyCode === 38) { // up
let i = consoleHistoryIndex;
const len = consoleHistory.length;
if (len === 0) {return;}
if (i < 0 || i > len) {
setConsoleHistoryIndex(len);
}
if (i !== 0) {
i = i-1;
}
setConsoleHistoryIndex(i);
const prevCommand = consoleHistory[i];
event.currentTarget.value = prevCommand;
}
if (event.keyCode === 40) {
const i = consoleHistoryIndex;
const len = consoleHistory.length;
if (len == 0) {return;}
if (i < 0 || i > len) {
setConsoleHistoryIndex(len);
}
// Latest command, put nothing
if (i == len || i == len-1) {
setConsoleHistoryIndex(len);
event.currentTarget.value = "";
} else {
setConsoleHistoryIndex(consoleHistoryIndex+1);
const prevCommand = consoleHistory[consoleHistoryIndex+1];
event.currentTarget.value = prevCommand;
}
}
}
return (<div ref={lastRef} className="bladeburner-console-div">
<table className="bladeburner-console-table">
<tbody>
{/*
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) => <Line key={i} content={log} />)}
<tr key="input" id="bladeburner-console-input-row" className="bladeburner-console-input-row">
<td className="bladeburner-console-input-cell">
<pre>{"> "}</pre><input autoFocus className="bladeburner-console-input" tabIndex={1} type="text" onKeyDown={handleKeyDown} />
</td>
</tr>
</tbody>
</table>
</div>);
}

@ -0,0 +1,139 @@
import React, { useState } from "react";
import { ActionTypes } from "../data/ActionTypes";
import { createProgressBarText } from "../../../utils/helpers/createProgressBarText";
import {
formatNumber,
convertTimeMsToTimeElapsedString,
} from "../../../utils/StringHelperFunctions";
import { stealthIcon, killIcon } from "../data/Icons";
import { BladeburnerConstants } from "../data/Constants";
import { IBladeburner } from "../IBladeburner";
import { IPlayer } from "../../PersonObjects/IPlayer";
interface IProps {
bladeburner: IBladeburner;
player: IPlayer;
action: any;
}
export function ContractElem(props: IProps): React.ReactElement {
const setRerender = useState(false)[1];
const isActive = props.bladeburner.action.type === ActionTypes["Contract"] && props.action.name === props.bladeburner.action.name;
const estimatedSuccessChance = props.action.getSuccessChance(props.bladeburner, {est:true});
const computedActionTimeCurrent = Math.min(props.bladeburner.actionTimeCurrent+props.bladeburner.actionTimeOverflow, props.bladeburner.actionTimeToComplete);
const maxLevel = (props.action.level >= props.action.maxLevel);
const actionTime = props.action.getActionTime(props.bladeburner);
const autolevelCheckboxId = `bladeburner-${props.action.name}-autolevel-checkbox`;
function onStart(): void {
props.bladeburner.action.type = ActionTypes.Contract;
props.bladeburner.action.name = 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 (<>
<h2 style={{display: 'inline-block'}}>
{isActive ?
<>{props.action.name} (IN PROGRESS - {formatNumber(computedActionTimeCurrent, 0)} / {formatNumber(props.bladeburner.actionTimeToComplete, 0)})</> :
<>{props.action.name}</>
}
</h2>
{isActive ?
<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>
</>}
<br />
<br />
<pre className="tooltip" style={{display:"inline-block"}}>
<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 />
<pre style={{display: 'inline-block'}}>
<span dangerouslySetInnerHTML={{__html: props.action.desc}} />
<br /><br />
Estimated success chance: {formatNumber(estimatedSuccessChance*100, 1)}% {props.action.isStealth?stealthIcon:<></>}${props.action.isKill?killIcon:<></>}<br />
Time Required: {convertTimeMsToTimeElapsedString(actionTime*1000)}<br />
Contracts remaining: {Math.floor(props.action.count)}<br />
Successes: {props.action.successes}<br />
Failures: {props.action.failures}
</pre>
<br />
<label
className="tooltip"
style={{color: 'white'}}
htmlFor={autolevelCheckboxId}>
Autolevel:
<span className="tooltiptext">Automatically increase operation level when possible</span>
</label>
<input
type="checkbox"
id={autolevelCheckboxId}
checked={props.action.autoLevel}
onChange={onAutolevel}/>
</>);
}
/*
// Autolevel Checkbox
el.appendChild(createElement("br"));
var autolevelCheckboxId = "bladeburner-" + action.name + "-autolevel-checkbox";
el.appendChild(createElement("label", {
for:autolevelCheckboxId, innerText:"Autolevel: ",color:"white",
tooltip:"Automatically increase contract level when possible",
}));
const checkboxInput = createElement("input", {
type:"checkbox",
id: autolevelCheckboxId,
checked: action.autoLevel,
changeListener: () => {
action.autoLevel = checkboxInput.checked;
},
});
el.appendChild(checkboxInput);
*/

@ -0,0 +1,20 @@
import React from "react";
import { ContractElem } from "./ContractElem";
import { IBladeburner } from "../IBladeburner";
import { IPlayer } from "../../PersonObjects/IPlayer";
interface IProps {
bladeburner: IBladeburner;
player: IPlayer;
}
export function ContractList(props: IProps): React.ReactElement {
const names = Object.keys(props.bladeburner.contracts);
const contracts = props.bladeburner.contracts;
return (<>
{names.map((name: string) => <li key={name} className="bladeburner-action">
<ContractElem bladeburner={props.bladeburner} action={contracts[name]} player={props.player} />
</li>,
)}
</>);
}

@ -0,0 +1,23 @@
import * as React from "react";
import { ContractList } from "./ContractList";
import { IBladeburner } from "../IBladeburner";
import { IPlayer } from "../../PersonObjects/IPlayer";
interface IProps {
bladeburner: IBladeburner;
player: IPlayer;
}
export function ContractPage(props: IProps): React.ReactElement {
return (<>
<p style={{display: 'block', margin: '4px', padding: '4px'}}>
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.
<br />
<br />
You can unlock higher-level contracts by successfully completing them.
Higher-level contracts are more difficult, but grant more rank, experience, and money.
</p>
<ContractList bladeburner={props.bladeburner} player={props.player} />
</>);
}

@ -0,0 +1,47 @@
import React, { useState } from "react";
import { ActionTypes } from "../data/ActionTypes";
import { createProgressBarText } from "../../../utils/helpers/createProgressBarText";
import { formatNumber } from "../../../utils/StringHelperFunctions";
import { IBladeburner } from "../IBladeburner";
import { IPlayer } from "../../PersonObjects/IPlayer";
interface IProps {
bladeburner: IBladeburner;
player: IPlayer;
action: any;
}
export function GeneralActionElem(props: IProps): React.ReactElement {
const setRerender = useState(false)[1];
const isActive = props.action.name === props.bladeburner.action.name;
const computedActionTimeCurrent = Math.min(props.bladeburner.actionTimeCurrent+props.bladeburner.actionTimeOverflow, props.bladeburner.actionTimeToComplete);
function onStart(): void {
props.bladeburner.action.type = ActionTypes[(props.action.name as string)];
props.bladeburner.action.name = props.action.name;
props.bladeburner.startAction(props.player, props.bladeburner.action);
setRerender(old => !old);
}
return (<>
<h2 style={{display: 'inline-block'}}>
{isActive ?
<>{props.action.name} (IN PROGRESS - {formatNumber(computedActionTimeCurrent, 0)} / {formatNumber(props.bladeburner.actionTimeToComplete, 0)})</> :
<>{props.action.name}</>
}
</h2>
{isActive ?
<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>
</>}
<br />
<br />
<pre style={{display: 'inline-block'}} dangerouslySetInnerHTML={{__html: props.action.desc}}></pre>
</>);
}

@ -0,0 +1,26 @@
import React from "react";
import { GeneralActionElem } from "./GeneralActionElem";
import { Action } from "../Action";
import { GeneralActions } from "../GeneralActions";
import { IBladeburner } from "../IBladeburner";
import { IPlayer } from "../../PersonObjects/IPlayer";
interface IProps {
bladeburner: IBladeburner;
player: IPlayer;
}
export function GeneralActionList(props: IProps): React.ReactElement {
const actions: Action[] = [];
for (const name in GeneralActions) {
if (GeneralActions.hasOwnProperty(name)) {
actions.push(GeneralActions[name]);
}
}
return (<>
{actions.map((action: Action) => <li key={action.name} className="bladeburner-action">
<GeneralActionElem bladeburner={props.bladeburner} action={action} player={props.player} />
</li>,
)}
</>);
}

@ -0,0 +1,19 @@
import * as React from "react";
import { GeneralActionList } from "./GeneralActionList";
import { IBladeburner } from "../IBladeburner";
import { IPlayer } from "../../PersonObjects/IPlayer";
interface IProps {
bladeburner: IBladeburner;
player: IPlayer;
}
export function GeneralActionPage(props: IProps): React.ReactElement {
return (<>
<p style={{display: 'block', margin: '4px', padding: '4px'}}>
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} />
</>);
}

@ -0,0 +1,133 @@
import React, { useState } from "react";
import { ActionTypes } from "../data/ActionTypes";
import { createProgressBarText } from "../../../utils/helpers/createProgressBarText";
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";
interface IProps {
bladeburner: IBladeburner;
player: IPlayer;
action: any;
}
export function OperationElem(props: IProps): React.ReactElement {
const setRerender = useState(false)[1];
const isActive = props.bladeburner.action.type === ActionTypes["Operation"] && props.action.name === props.bladeburner.action.name;
const estimatedSuccessChance = props.action.getSuccessChance(props.bladeburner, {est:true});
const computedActionTimeCurrent = Math.min(props.bladeburner.actionTimeCurrent+props.bladeburner.actionTimeOverflow,props.bladeburner.actionTimeToComplete);
const maxLevel = (props.action.level >= props.action.maxLevel);
const actionTime = props.action.getActionTime(props.bladeburner);
const autolevelCheckboxId = `bladeburner-${props.action.name}-autolevel-checkbox`;
function onStart(): void {
props.bladeburner.action.type = ActionTypes.Operation;
props.bladeburner.action.name = 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 (<>
<h2 style={{display: 'inline-block'}}>
{isActive ?
<>{props.action.name} (IN PROGRESS - {formatNumber(computedActionTimeCurrent, 0)} / {formatNumber(props.bladeburner.actionTimeToComplete, 0)})</> :
<>{props.action.name}</>
}
</h2>
{isActive ?
<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>
<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 />
<pre className="tooltip" style={{display:"inline-block"}}>
<span className="tooltiptext">
{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 />
<pre style={{display:"inline-block"}}>
<span dangerouslySetInnerHTML={{__html: props.action.desc}} />
<br /><br />
Estimated success chance: {formatNumber(estimatedSuccessChance*100, 1)}% {props.action.isStealth?stealthIcon:<></>}{props.action.isKill?killIcon:<></>}<br />
Time Required: {convertTimeMsToTimeElapsedString(actionTime*1000)}<br />
Operations remaining: {Math.floor(props.action.count)}<br />
Successes: {props.action.successes}<br />
Failures: {props.action.failures}
</pre>
<br />
<label
className="tooltip"
style={{color: 'white'}}
htmlFor={autolevelCheckboxId}>
Autolevel:
<span className="tooltiptext">Automatically increase operation level when possible</span>
</label>
<input
type="checkbox"
id={autolevelCheckboxId}
checked={props.action.autoLevel}
onChange={onAutolevel}/>
</>);
}

@ -0,0 +1,20 @@
import React from "react";
import { OperationElem } from "./OperationElem";
import { IBladeburner } from "../IBladeburner";
import { IPlayer } from "../../PersonObjects/IPlayer";
interface IProps {
bladeburner: IBladeburner;
player: IPlayer;
}
export function OperationList(props: IProps): React.ReactElement {
const names = Object.keys(props.bladeburner.operations);
const operations = props.bladeburner.operations;
return (<>
{names.map((name: string) => <li key={name} className="bladeburner-action">
<OperationElem bladeburner={props.bladeburner} action={operations[name]} player={props.player} />
</li>,
)}
</>);
}

@ -0,0 +1,34 @@
import * as React from "react";
import { OperationList } from "./OperationList";
import { IBladeburner } from "../IBladeburner";
import { IPlayer } from "../../PersonObjects/IPlayer";
interface IProps {
bladeburner: IBladeburner;
player: IPlayer;
}
export function OperationPage(props: IProps): React.ReactElement {
return (<>
<p style={{display: 'block', margin: '4px', padding: '4px'}}>
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 punishing than contracts,
but are also more rewarding.
<br />
<br />
Operations can affect the chaos level and Synthoid population of your
current city. The exact effects vary between different Operations.
<br />
<br />
For operations, you can use a team. You must first recruit team members.
Having a larger team will improves your chances of success.
<br />
<br />
You can unlock higher-level operations by successfully completing them.
Higher-level operations are more difficult, but grant more rank and experience.
</p>
<OperationList bladeburner={props.bladeburner} player={props.player} />
</>);
}

@ -0,0 +1,28 @@
import React from "react";
import { Stats } from "./Stats";
import { Console } from "./Console";
import { AllPages } from "./AllPages";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { IEngine } from "../../IEngine";
import { IBladeburner } from "../IBladeburner";
interface IProps {
bladeburner: IBladeburner;
engine: IEngine;
player: IPlayer;
}
export function Root(props: IProps): React.ReactElement {
return (<div id="bladeburner-container">
<div style={{height:"60%", display:"block", position:"relative"}}>
<div style={{height: '100%', width:"30%", display:"inline-block", border:"1px solid white"}}>
<Stats bladeburner={props.bladeburner} player={props.player} engine={props.engine} />
</div>
<Console bladeburner={props.bladeburner} player={props.player} />
</div>
<div style={{width:"70%", display:"block", border:"1px solid white", marginTop:"6px", padding: "6px", position:"relative"}}>
<AllPages bladeburner={props.bladeburner} player={props.player} />
</div>
</div>);
}

@ -0,0 +1,48 @@
import React from "react";
import { CopyableText } from "../../ui/React/CopyableText";
import { formatNumber } from "../../../utils/StringHelperFunctions";
import { IBladeburner } from "../IBladeburner";
interface IProps {
skill: any;
bladeburner: IBladeburner;
onUpgrade: () => void;
}
export function SkillElem(props: IProps): React.ReactElement {
const skillName = props.skill.name;
let currentLevel = 0;
if (props.bladeburner.skills[skillName] && !isNaN(props.bladeburner.skills[skillName])) {
currentLevel = props.bladeburner.skills[skillName];
}
const pointCost = props.skill.calculateCost(currentLevel);
const canLevel = props.bladeburner.skillPoints >= pointCost;
const maxLvl = props.skill.maxLvl ? currentLevel >= props.skill.maxLvl : false;
function onClick(): void {
if (props.bladeburner.skillPoints < pointCost) return;
props.bladeburner.skillPoints -= pointCost;
props.bladeburner.upgradeSkill(props.skill);
props.onUpgrade();
}
return (<>
<h2 style={{display: 'inline-block'}}>
<CopyableText value={props.skill.name} />
</h2>
<a
onClick={onClick}
style={{display: "inline-block", margin: "3px", padding: "3px"}}
className={canLevel && !maxLvl ? "a-link-button" : "a-link-button-inactive"}>
Level
</a>
<br />
<br />
<p style={{display: 'block'}}>Level: {currentLevel}</p>
{maxLvl ?
<p style={{color:"red", display:"block"}}>MAX LEVEL</p> :
<p style={{display:"block"}}>Skill Points required: {formatNumber(pointCost, 0)}</p>}
<p style={{display:"inline-block"}} dangerouslySetInnerHTML={{__html: props.skill.desc}} />
</>);
}

@ -0,0 +1,18 @@
import * as React from "react";
import { SkillElem } from "./SkillElem";
import { Skills } from "../Skills";
import { IBladeburner } from "../IBladeburner";
interface IProps {
bladeburner: IBladeburner;
onUpgrade: () => void;
}
export function SkillList(props: IProps): React.ReactElement {
return (<>
{Object.keys(Skills).map((skill: string) => <li key={skill} className="bladeburner-action">
<SkillElem bladeburner={props.bladeburner} skill={Skills[skill]} onUpgrade={props.onUpgrade} />
</li>,
)}
</>);
}

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

@ -0,0 +1,139 @@
import React, { useState, useEffect } from "react";
import {
formatNumber,
convertTimeMsToTimeElapsedString,
} from "../../../utils/StringHelperFunctions";
import { BladeburnerConstants } from "../data/Constants";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { IEngine } from "../../IEngine";
import { Money } from "../../ui/React/Money";
import { StatsTable } from "../../ui/React/StatsTable";
import { numeralWrapper } from "../../ui/numeralFormat";
import { dialogBoxCreate } from "../../../utils/DialogBox";
import { createPopup } from "../../ui/React/createPopup";
import { Factions } from "../../Faction/Factions";
import {
joinFaction,
displayFactionContent,
} from "../../Faction/FactionHelpers";
import { IBladeburner } from "../IBladeburner"
import { TravelPopup } from "./TravelPopup";
interface IProps {
bladeburner: IBladeburner;
engine: IEngine;
player: IPlayer;
}
export function Stats(props: IProps): React.ReactElement {
const setRerender = useState(false)[1];
useEffect(() => {
const id = setInterval(() => setRerender(old => !old), 1000);
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 {
const faction = Factions["Bladeburners"];
if (faction.isMember) {
props.engine.loadFactionContent();
displayFactionContent("Bladeburners");
} 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!")
}
}
}
return (<>
<p className="tooltip" style={{display: 'inline-block'}}>
Rank: {formatNumber(props.bladeburner.rank, 2)}
<span className="tooltiptext">Your rank within the Bladeburner division.</span>
</p><br />
<p>Stamina: {formatNumber(props.bladeburner.stamina, 3)} / {formatNumber(props.bladeburner.maxStamina, 3)}</p>
<div className="help-tip" onClick={openStaminaHelp}>?</div><br />
<p>Stamina Penalty: {formatNumber((1-props.bladeburner.calculateStaminaPenalty())*100, 1)}%</p><br />
<p>Team Size: {formatNumber(props.bladeburner.teamSize, 0)}</p>
<p>Team Members Lost: {formatNumber(props.bladeburner.teamLost, 0)}</p><br />
<p>Num Times Hospitalized: {props.bladeburner.numHosp}</p>
<p>Money Lost From Hospitalizations: {Money(props.bladeburner.moneyLost)}</p><br />
<p>Current City: {props.bladeburner.city}</p>
<p className="tooltip" style={{display: 'inline-block'}}>
Est. Synthoid Population: {numeralWrapper.formatPopulation(props.bladeburner.getCurrentCity().popEst)}
<span className="tooltiptext">This is your Bladeburner division's estimate of how many Synthoids exist in your current city.</span>
</p>
<div className="help-tip" onClick={openPopulationHelp}>?</div><br />
<p className="tooltip" style={{display: 'inline-block'}}>
Est. Synthoid Communities: {formatNumber(props.bladeburner.getCurrentCity().comms, 0)}
<span className="tooltiptext">This is your Bladeburner divison's estimate of how many Synthoid communities exist in your current city.</span>
</p><br />
<p className="tooltip" style={{display: 'inline-block'}}>
City Chaos: {formatNumber(props.bladeburner.getCurrentCity().chaos)}
<span className="tooltiptext">The city's chaos level due to tensions and conflicts between humans and Synthoids. Having too high of a chaos level can make contracts and operations harder.</span>
</p><br />
<br />
<p className="tooltip" style={{display: 'inline-block'}}>
Bonus time: {convertTimeMsToTimeElapsedString(props.bladeburner.storedCycles/BladeburnerConstants.CyclesPerSecond*1000)}<br />
<span className="tooltiptext">
You gain bonus time while offline or when the game is inactive (e.g. when the tab is throttled by browser).
Bonus time makes the Bladeburner mechanic progress faster, up to 5x the normal speed.
</span>
</p>
<p>Skill Points: {formatNumber(props.bladeburner.skillPoints, 0)}</p><br />
{StatsTable([
["Aug. Success Chance mult: ", formatNumber(props.player.bladeburner_success_chance_mult*100, 1) + "%"],
["Aug. Max Stamina mult: ", formatNumber(props.player.bladeburner_max_stamina_mult*100, 1) + "%"],
["Aug. Stamina Gain mult: ", formatNumber(props.player.bladeburner_stamina_gain_mult*100, 1) + "%"],
["Aug. Field Analysis mult: ", formatNumber(props.player.bladeburner_analysis_mult*100, 1) + "%"],
])}
<br />
<a onClick={openTravel} className="a-link-button" style={{display:"inline-block"}}>Travel</a>
<a onClick={openFaction} className="a-link-button tooltip" style={{display:"inline-block"}}>
<span className="tooltiptext">Apply to the Bladeburner Faction, or go to the faction page if you are already a member</span>
Faction
</a>
<br />
<br />
</>);
}

@ -0,0 +1,37 @@
import React, { useState } from "react";
import { removePopup } from "../../ui/React/createPopup";
import { dialogBoxCreate } from "../../../utils/DialogBox";
import { Action } from "../Action";
import { IBladeburner } from "../IBladeburner";
interface IProps {
bladeburner: IBladeburner;
action: Action;
popupId: string;
}
export function TeamSizePopup(props: IProps): React.ReactElement {
const [teamSize, setTeamSize] = useState<number | undefined>();
function confirmTeamSize(): void {
if(teamSize === undefined) return;
const num = Math.round(teamSize);
if (isNaN(num) || num < 0) {
dialogBoxCreate("Invalid value entered for number of Team Members (must be numeric, positive)")
} else {
props.action.teamCount = num;
}
removePopup(props.popupId);
}
return (<>
<p>
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.
</p>
<input autoFocus type="number" placeholder= "Team size" className= "text-input" onChange={event => setTeamSize(parseFloat(event.target.value))} />
<a className="a-link-button" onClick={confirmTeamSize}>Confirm</a>
</>);
}

@ -0,0 +1,32 @@
import React from "react";
import { removePopup } from "../../ui/React/createPopup";
import { BladeburnerConstants } from "../data/Constants";
import { IBladeburner } from "../IBladeburner";
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>
{BladeburnerConstants.CityNames.map(city => {
// Reusing this css class...it adds a border and makes it
// so that background color changes when you hover
return <div key={city} className="cmpy-mgmt-find-employee-option"
onClick={() => travel(city)}>
{city}
</div>})}
</>);
}

@ -6,7 +6,7 @@
import { IMap } from "./types";
export const CONSTANTS: IMap<any> = {
Version: "0.52.3",
Version: "0.52.4",
// Speed (in ms) at which the main loop is updated
_idleSpeed: 200,
@ -228,26 +228,26 @@ export const CONSTANTS: IMap<any> = {
TotalNumBitNodes: 24,
LatestUpdate: `
v0.52.3 - 2021-07-15 Gangs were OP (hydroflame)
v0.52.4 - 2021-07-19 Bladeburner in React (hydroflame)
-------------------------------------------
** Gang **
** Bladeburner **
* Significant rework. Ascension is now based on exp gained.
* All upgrades give exp bonuses.
* Maximum gang members reduced to 12.
* Respect required to recruit sharply increased.
* Rewritten in React, the UI should be smoother and less laggy now.
* The entire UI was rebuild in React. It should be more responsive
** Infiltration **
** Hacknet **
* Now isTrusted protected.
* Displays how many time each hash upgrade was bought.
* Displays cummulative effect of the upgrade.
* Removed "Close" button from hash upgrade menu.
** Misc. **
* Many UI element are now "noselect" protected.
* Fixed an issue where you could join the same faction twice via script and
UI simultaneously.
* Factions list screen converted to React.
* More popup/modals have dark background, can be dismissed by clicking
outside, or by pressing escape.
* Small reword in the guide.
* Fix several typos in the bladeburner documentation.
* Linting (no one cares except the dev)
* nerf noodle bar
`,
}

@ -14,7 +14,7 @@ import { AllServers } from "./Server/AllServers";
import { GetServerByHostname } from "./Server/ServerHelpers";
import { hackWorldDaemon } from "./RedPill";
import { StockMarket } from "./StockMarket/StockMarket";
import { Bladeburner } from "./Bladeburner";
import { Bladeburner } from "./Bladeburner/Bladeburner";
import { Stock } from "./StockMarket/Stock";
import { Engine } from "./engine";
import { saveObject } from "./SaveObject";

@ -28,8 +28,7 @@ export function FactionList(props: IProps): React.ReactElement {
<p>Lists all factions you have joined</p>
<br />
<ul>
{props.player.factions.map((faction: string) =>
<li key={faction}><a
{props.player.factions.map((faction: string) => <li key={faction}><a
className="a-link-button"
onClick={() => openFaction(faction)}
style={{padding:"4px", margin:"4px", display:"inline-block"}}>{faction}
@ -39,8 +38,7 @@ export function FactionList(props: IProps): React.ReactElement {
<h1>Outstanding Faction Invitations</h1>
<p style={{width: '70%'}}>Lists factions you have been invited to. You can accept these faction invitations at any time.</p>
<ul>
{props.player.factionInvitations.map((faction: string) =>
<li key={faction} style={{padding:"6px", margin:"6px"}}>
{props.player.factionInvitations.map((faction: string) => <li key={faction} style={{padding:"6px", margin:"6px"}}>
<p style={{display:"inline", margin:"4px", padding:"4px"}}>{faction}</p>
<a
className="a-link-button"

@ -8,8 +8,6 @@
import { Faction } from "../Faction/Faction";
import { Factions } from "../Faction/Factions";
import { numeralWrapper } from "../ui/numeralFormat";
import { dialogBoxCreate } from "../../utils/DialogBox";
import {
Reviver,

@ -4,7 +4,6 @@ import { GangMemberUpgrade } from "./GangMemberUpgrade";
import { GangMemberUpgrades } from "./GangMemberUpgrades";
import { IAscensionResult } from "./IAscensionResult";
import { IPlayer } from "../PersonObjects/IPlayer";
import { GangConstants } from "./data/Constants";
import { AllGangs } from "./AllGangs";
import { IGang } from "./IGang";
import { Generic_fromJSON, Generic_toJSON, Reviver } from "../../utils/JSONReviver";

@ -42,6 +42,6 @@ export function TaskSelector(props: IProps): React.ReactElement {
<option key={0} value={"---"}>---</option>
{tasks.map((task: string, i: number) => <option key={i+1} value={task}>{task}</option>)}
</select>
<div>{StatsTable(data, null)}</div>
<div>{StatsTable(data)}</div>
</>);
}

@ -542,7 +542,7 @@ export function purchaseHashUpgrade(upgName, upgTarget) {
case "Exchange for Bladeburner Rank": {
// This will throw if player isnt in Bladeburner
try {
Player.bladeburner.changeRank(upg.value);
Player.bladeburner.changeRank(Player, upg.value);
} catch(e) {
Player.hashManager.refundUpgrade(upgName);
return false;

@ -8,6 +8,7 @@ export interface IConstructorParams {
hasTargetServer?: boolean;
name: string;
value: number;
effectText?: (level: number) => JSX.Element | null;
}
export class HashUpgrade {
@ -43,8 +44,11 @@ export class HashUpgrade {
// The meaning varies between different upgrades
value = 0;
effectText: (level: number) => JSX.Element | null = () => null;
constructor(p: IConstructorParams) {
if (p.cost != null) { this.cost = p.cost; }
if (p.effectText != null) { this.effectText = p.effectText; }
this.costPerLevel = p.costPerLevel;
this.desc = p.desc;

@ -1,5 +1,8 @@
// Metadata used to construct all Hash Upgrades
import React from "react";
import { IConstructorParams } from "../HashUpgrade";
import { numeralWrapper } from "../../ui/numeralFormat";
import { Money } from "../../ui/React/Money";
export const HashUpgradesMetadata: IConstructorParams[] = [
{
@ -7,12 +10,14 @@ export const HashUpgradesMetadata: IConstructorParams[] = [
costPerLevel: 4,
desc: "Sell hashes for $1m",
name: "Sell for Money",
effectText: (level: number): JSX.Element | null => (<>Sold for {Money(1e6*level)}</>),
value: 1e6,
},
{
costPerLevel: 100,
desc: "Sell hashes for $1b in Corporation funds",
name: "Sell for Corporation Funds",
effectText: (level: number): JSX.Element | null => (<>Sold for {Money(1e9*level)} Corporation funds.</>),
value: 1e9,
},
{
@ -38,6 +43,7 @@ export const HashUpgradesMetadata: IConstructorParams[] = [
desc: "Use hashes to improve the experience earned when studying at a university by 20%. " +
"This effect persists until you install Augmentations",
name: "Improve Studying",
//effectText: (level: number) => JSX.Element | null = <>Improves studying by ${level*20}%</>,
value: 20, // Improves studying by value%
},
{
@ -45,30 +51,35 @@ export const HashUpgradesMetadata: IConstructorParams[] = [
desc: "Use hashes to improve the experience earned when training at the gym by 20%. This effect " +
"persists until you install Augmentations",
name: "Improve Gym Training",
effectText: (level: number): JSX.Element | null => (<>Improves training by ${level*20}%</>),
value: 20, // Improves training by value%
},
{
costPerLevel: 200,
desc: "Exchange hashes for 1k Scientific Research in all of your Corporation's Industries",
name: "Exchange for Corporation Research",
effectText: (level: number): JSX.Element | null => (<>Acquired a total of {level}k Scientific Research in your industries.</>),
value: 1000,
},
{
costPerLevel: 250,
desc: "Exchange hashes for 100 Bladeburner Rank",
name: "Exchange for Bladeburner Rank",
effectText: (level: number): JSX.Element | null => (<>Acquired a total of {numeralWrapper.format(100*level, '0a')} Bladeburner rank</>),
value: 100,
},
{
costPerLevel: 250,
desc: "Exchanges hashes for 10 Bladeburner Skill Points",
name: "Exchange for Bladeburner SP",
effectText: (level: number): JSX.Element | null => (<>Acquired a total of {numeralWrapper.format(10*level, '0a')} Bladeburner Skill Points</>),
value: 10,
},
{
costPerLevel: 200,
desc: "Generate a random Coding Contract somewhere on the network",
name: "Generate Coding Contract",
effectText: (level: number): JSX.Element | null => (<>Generated {level} contracts.</>),
value: 1,
},
]

@ -54,6 +54,8 @@ class HashUpgrade extends React.Component {
const hashManager = this.props.hashManager;
const upg = this.props.upg;
const cost = hashManager.getUpgradeCost(upg.name);
const level = hashManager.upgrades[upg.name];
const effect = upg.effectText(level);
// Purchase button
const canPurchase = hashManager.hashes >= cost;
@ -63,11 +65,13 @@ class HashUpgrade extends React.Component {
return (
<div className={"bladeburner-action"}>
<CopyableText value={upg.name} />
<p>Cost: {Hashes(cost)}</p>
<p>Cost: {Hashes(cost)}, Bought: {level} times</p>
<p>{upg.desc}</p>
<button className={btnClass} onClick={this.purchase}>
Purchase
</button>
{level > 0 && effect && <p>{effect}</p>}
{
upg.hasTargetServer &&
<ServerDropdown
@ -119,7 +123,6 @@ export class HashUpgradePopup extends React.Component {
return (
<div>
<PopupCloseButton popup={this.props.popupId} text={"Close"} />
<p>Spend your hashes on a variety of different upgrades</p>
<p>Hashes: {numeralWrapper.formatHashes(this.state.totalHashes)}</p>
{upgradeElems}

@ -14,13 +14,6 @@ interface IProps {
cancel: () => void;
}
function diffStr(d: number): string {
if(d<=0.5) return "trivial";
if(d<=1.5) return "normal";
if(d<=2.5) return "hard";
return "impossible";
}
function arrowPart(color: string, length: number): JSX.Element {
let arrow = "";
if(length <= 0) length = 0;

@ -7,7 +7,7 @@ import { Player } from "../Player";
import { redPillFlag } from "../RedPill";
import { GetServerByHostname } from "../Server/ServerHelpers";
import { Settings } from "../Settings/Settings";
import { dialogBoxCreate, dialogBoxOpened} from "../../utils/DialogBox";
import { dialogBoxCreate} from "../../utils/DialogBox";
import { Reviver } from "../../utils/JSONReviver";
//Sends message to player, including a pop up
@ -59,12 +59,10 @@ function checkForMessagesToSend() {
}
if (redpill && redpillOwned && Player.sourceFiles.length === 0 && !redPillFlag && !inMission) {
if (!dialogBoxOpened) {
sendMessage(redpill, true);
}
sendMessage(redpill, true);
} else if (redpill && redpillOwned) {
//If player has already destroyed a BitNode, message is not forced
if (!redPillFlag && !inMission && !dialogBoxOpened) {
if (!redPillFlag && !inMission) {
sendMessage(redpill);
}
} else if (jumper0 && !jumper0.recvd && Player.hacking_skill >= 25) {

@ -13,7 +13,7 @@ import { prestigeAugmentation } from "./Prestige";
import { AugmentationNames } from "./Augmentation/data/AugmentationNames";
import { BitNodeMultipliers } from "./BitNode/BitNodeMultipliers";
import { findCrime } from "./Crime/CrimeHelpers";
import { Bladeburner } from "./Bladeburner";
import { Bladeburner } from "./Bladeburner/Bladeburner";
import { Company } from "./Company/Company";
import { Companies } from "./Company/Companies";
import { CompanyPosition } from "./Company/CompanyPosition";
@ -4046,7 +4046,7 @@ function NetscriptFunctions(workerScript) {
return true; // Already member
} else if (Player.strength >= 100 && Player.defense >= 100 &&
Player.dexterity >= 100 && Player.agility >= 100) {
Player.bladeburner = new Bladeburner({new:true});
Player.bladeburner = new Bladeburner();
workerScript.log("joinBladeburnerDivision", "You have been accepted into the Bladeburner division");
const worldHeader = document.getElementById("world-menu-header");

@ -43,6 +43,7 @@ export interface IPlayer {
homeComputer: string;
hp: number;
jobs: IMap<string>;
isWorking: boolean;
karma: number;
location: LocationName;
max_hp: number;
@ -136,6 +137,7 @@ export interface IPlayer {
gainDexterityExp(exp: number): void;
gainAgilityExp(exp: number): void;
gainCharismaExp(exp: number): void;
gainIntelligenceExp(exp: number): void;
gainMoney(money: number): void;
getCurrentServer(): Server;
getGangFaction(): Faction;

@ -1,4 +1,4 @@
import { Bladeburner } from "../../Bladeburner";
import { Bladeburner } from "../../Bladeburner/Bladeburner";
import { SourceFileFlags } from "../../SourceFile/SourceFileFlags";
export function canAccessBladeburner() {
@ -13,5 +13,5 @@ export function inBladeburner() {
}
export function startBladeburner() {
this.bladeburner = new Bladeburner({ new: true });
this.bladeburner = new Bladeburner(this);
}

@ -5,7 +5,7 @@ import {
} from "./Augmentation/AugmentationHelpers";
import { AugmentationNames } from "./Augmentation/data/AugmentationNames";
import { initBitNodeMultipliers } from "./BitNode/BitNode";
import { Bladeburner } from "./Bladeburner";
import { Bladeburner } from "./Bladeburner/Bladeburner";
import { writeCinematicText } from "./CinematicText";
import { Companies, initCompanies } from "./Company/Companies";
import { resetIndustryResearchTrees } from "./Corporation/IndustryData";

2
src/RedPill.d.ts vendored Normal file

@ -0,0 +1,2 @@
export declare let redPillFlag: boolean;
export declare function hackWorldDaemon(currentNodeNumber: number, flume: boolean = false, quick: boolean = false): void;

@ -30,7 +30,7 @@ import { dialogBoxCreate } from "../../utils/DialogBox";
import { compareArrays } from "../../utils/helpers/compareArrays";
import { createElement } from "../../utils/uiHelpers/createElement";
var scriptEditorRamCheck = null, scriptEditorRamText = null;
var scriptEditorRamText = null;
export function scriptEditorInit() {
// Wrapper container that holds all the buttons below the script editor
const wrapper = document.getElementById("script-editor-buttons-wrapper");
@ -58,22 +58,6 @@ export function scriptEditorInit() {
display:"inline-block", margin:"10px", id:"script-editor-status-text",
});
// Label for checkbox (defined below)
const checkboxLabel = createElement("label", {
for:"script-editor-ram-check", margin:"4px", marginTop: "8px",
innerText:"Dynamic RAM Usage Checker", color:"white",
tooltip:"Enable/Disable the dynamic RAM Usage display. You may " +
"want to disable it for very long scripts because there may be " +
"performance issues",
});
// Checkbox for enabling/disabling dynamic RAM calculation
scriptEditorRamCheck = createElement("input", {
type:"checkbox", name:"script-editor-ram-check", id:"script-editor-ram-check",
margin:"4px", marginTop: "8px",
});
scriptEditorRamCheck.checked = true;
// Link to Netscript documentation
const documentationButton = createElement("a", {
class: "std-button",
@ -98,8 +82,6 @@ export function scriptEditorInit() {
wrapper.appendChild(beautifyButton);
wrapper.appendChild(closeButton);
wrapper.appendChild(scriptEditorRamText);
wrapper.appendChild(scriptEditorRamCheck);
wrapper.appendChild(checkboxLabel);
wrapper.appendChild(documentationButton);
// Initialize editors
@ -170,8 +152,8 @@ export function getCurrentEditor() {
//Updates RAM usage in script
export async function updateScriptEditorContent() {
var filename = document.getElementById("script-editor-filename").value;
if (scriptEditorRamCheck == null || !scriptEditorRamCheck.checked || !isScriptFilename(filename)) {
scriptEditorRamText.innerText = "N/A";
if (!isScriptFilename(filename)) {
scriptEditorRamText.innerText = "RAM: N/A";
return;
}

@ -17,7 +17,7 @@ import { AugmentationNames } from "./Augmentation/data/AugmentationNames";
import {
initBitNodeMultipliers,
} from "./BitNode/BitNode";
import { Bladeburner } from "./Bladeburner";
import { Bladeburner } from "./Bladeburner/Bladeburner";
import { CharacterOverviewComponent } from "./ui/React/CharacterOverview";
import { cinematicTextFlag } from "./CinematicText";
import { generateRandomContract } from "./CodingContractGenerator";
@ -27,8 +27,6 @@ import { CONSTANTS } from "./Constants";
import { createDevMenu, closeDevMenu } from "./DevMenu";
import { Factions, initFactions } from "./Faction/Factions";
import {
displayFactionContent,
joinFaction,
processPassiveFactionRepGain,
inviteToFaction,
} from "./Faction/FactionHelpers";
@ -36,6 +34,7 @@ import {
FactionList,
} from "./Faction/ui/FactionList";
import { displayGangContent } from "./Gang/Helpers";
import { Root as BladeburnerRoot } from "./Bladeburner/ui/Root";
import { displayInfiltrationContent } from "./Infiltration/Helper";
import {
getHackingWorkRepGain,
@ -109,8 +108,6 @@ import { initializeMainMenuLinks, MainMenuLinks } from "./ui/MainMenu/Links";
import { dialogBoxCreate } from "../utils/DialogBox";
import { gameOptionsBoxClose, gameOptionsBoxOpen } from "../utils/GameOptions";
import { removeChildrenFromElement } from "../utils/uiHelpers/removeChildrenFromElement";
import { createElement } from "../utils/uiHelpers/createElement";
import { exceptionAlert } from "../utils/helpers/exceptionAlert";
import { removeLoadingScreen } from "../utils/uiHelpers/removeLoadingScreen";
import { KEY } from "../utils/helpers/keyCodes";
@ -229,6 +226,7 @@ const Engine = {
infiltrationContent: null,
stockMarketContent: null,
gangContent: null,
bladeburnerContent: null,
locationContent: null,
workInProgressContent: null,
redPillContent: null,
@ -470,15 +468,15 @@ const Engine = {
},
loadBladeburnerContent: function() {
if (Player.bladeburner instanceof Bladeburner) {
try {
Engine.hideAllContent();
routing.navigateTo(Page.Bladeburner);
Player.bladeburner.createContent();
} catch(e) {
exceptionAlert(e);
}
}
if (!(Player.bladeburner instanceof Bladeburner)) return;
Engine.hideAllContent();
routing.navigateTo(Page.Bladeburner);
Engine.Display.bladeburnerContent.style.display = "block";
ReactDOM.render(
<BladeburnerRoot bladeburner={Player.bladeburner} player={Player} engine={this} />,
Engine.Display.bladeburnerContent,
);
MainMenuLinks.Bladeburner.classList.add("active");
},
loadSleevesContent: function() {
@ -534,6 +532,9 @@ const Engine = {
Engine.Display.gangContent.style.display = "none";
ReactDOM.unmountComponentAtNode(Engine.Display.gangContent);
Engine.Display.bladeburnerContent.style.display = "none";
ReactDOM.unmountComponentAtNode(Engine.Display.bladeburnerContent);
Engine.Display.workInProgressContent.style.display = "none";
Engine.Display.redPillContent.style.display = "none";
Engine.Display.cinematicTextContent.style.display = "none";
@ -547,10 +548,6 @@ const Engine = {
Player.corporation.clearUI();
}
if (Player.bladeburner instanceof Bladeburner) {
Player.bladeburner.clearContent();
}
clearResleevesPage();
clearSleevesPage();
@ -606,9 +603,9 @@ const Engine = {
// Main Game Loop
idleTimer: function() {
// Get time difference
var _thisUpdate = new Date().getTime();
var diff = _thisUpdate - Engine._lastUpdate;
var offset = diff % Engine._idleSpeed;
const _thisUpdate = new Date().getTime();
let diff = _thisUpdate - Engine._lastUpdate;
const offset = diff % Engine._idleSpeed;
// Divide this by cycle time to determine how many cycles have elapsed since last update
diff = Math.floor(diff / Engine._idleSpeed);
@ -624,7 +621,7 @@ const Engine = {
},
updateGame: function(numCycles = 1) {
var time = numCycles * Engine._idleSpeed;
const time = numCycles * Engine._idleSpeed;
if (Player.totalPlaytime == null) {Player.totalPlaytime = 0;}
if (Player.playtimeSinceLastAug == null) {Player.playtimeSinceLastAug = 0;}
if (Player.playtimeSinceLastBitnode == null) {Player.playtimeSinceLastBitnode = 0;}
@ -882,7 +879,7 @@ const Engine = {
}
if (Player.bladeburner instanceof Bladeburner) {
try {
Player.bladeburner.process();
Player.bladeburner.process(Player);
} catch(e) {
exceptionAlert("Exception caught in Bladeburner.process(): " + e);
}
@ -1259,6 +1256,9 @@ const Engine = {
Engine.Display.gangContent = document.getElementById("gang-container");
Engine.Display.gangContent.style.display = "none";
Engine.Display.bladeburnerContent = document.getElementById("gang-container");
Engine.Display.bladeburnerContent.style.display = "none";
Engine.Display.missionContent = document.getElementById("mission-container");
Engine.Display.missionContent.style.display = "none";

@ -232,8 +232,10 @@ if (htmlWebpackPlugin.options.googleAnalytics.trackingId) { %>
<div id="augmentations-container" class="generic-menupage-container"></div>
<!-- Milestones content -->
<div id="milestones-container" class="generic-menupage-container">
</div>
<div id="milestones-container" class="generic-menupage-container"></div>
<!-- Bladeburner -->
<div id="bladeburner-container" class="generic-menupage-container"></div>
<!-- Tutorial content -->
<div id="tutorial-container" class="generic-menupage-container">

@ -63,7 +63,7 @@ export function CharacterInfo(p: IPlayer): React.ReactElement {
if (src.casino) { parts.push([`Casino:`, Money(src.casino)]) }
if (src.sleeves) { parts.push([`Sleeves:`, Money(src.sleeves)]) }
return StatsTable(parts, "");
return StatsTable(parts);
}
function openMoneyModal(): void {
@ -254,7 +254,7 @@ export function CharacterInfo(p: IPlayer): React.ReactElement {
<span>{`Servers owned: ${p.purchasedServers.length} / ${getPurchaseServerLimit()}`}</span><br />
<Hacknet />
<span>{`Augmentations installed: ${p.augmentations.length}`}</span><br /><br />
{StatsTable(timeRows, null)}
{StatsTable(timeRows)}
<br />
<CurrentBitNode />
</pre>

@ -3,15 +3,27 @@
*
* Takes in a prop for rendering the content inside the popup
*/
import * as React from "react";
import React, { useEffect } from "react";
interface IProps<T> {
content: (props: T) => React.ReactElement;
id: string;
props: T;
removePopup: (id: string) => void;
}
export function Popup<T>(props: IProps<T>): React.ReactElement {
function keyDown(event: KeyboardEvent): void {
if(event.key === 'Escape') props.removePopup(props.id);
}
useEffect(() => {
document.addEventListener('keydown', keyDown);
return () => {
document.removeEventListener('keydown', keyDown);
}
});
return (
<div className={"popup-box-content"} id={`${props.id}-content`}>
{React.createElement(props.content, props.props)}

@ -1,6 +1,6 @@
import * as React from "react";
export function StatsTable(rows: any[][], title: string | null): React.ReactElement {
export function StatsTable(rows: any[][], title?: string): React.ReactElement {
let titleElem = <></>
if (title) {
titleElem = <><h2><u>{title}</u></h2><br /></>;

@ -16,20 +16,27 @@ import { removeElementById } from "../../../utils/uiHelpers/removeElementById";
let gameContainer: HTMLElement;
function getGameContainer(): void {
const container = document.getElementById("entire-game-container");
if (container == null) {
throw new Error(`Failed to find game container DOM element`)
(function() {
function getGameContainer(): void {
const container = document.getElementById("entire-game-container");
if (container == null) {
throw new Error(`Failed to find game container DOM element`)
}
gameContainer = container;
document.removeEventListener("DOMContentLoaded", getGameContainer);
}
gameContainer = container;
document.removeEventListener("DOMContentLoaded", getGameContainer);
}
document.addEventListener("DOMContentLoaded", getGameContainer);
})();
document.addEventListener("DOMContentLoaded", getGameContainer);
// This variable is used to avoid setting the semi-transparent background
// several times on top of one another. Sometimes there's several popup at once.
let deepestPopupId = "";
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
export function createPopup<T>(id: string, rootComponent: (props: T) => React.ReactElement, props: T): HTMLElement | null {
let container = document.getElementById(id);
if (container == null) {
function onClick(this: HTMLElement, event: MouseEvent): any {
@ -39,19 +46,20 @@ export function createPopup<T>(id: string, rootComponent: (props: T) => React.Re
if(clickedId !== id) return;
removePopup(id);
}
const backgroundColor = deepestPopupId === "" ? 'rgba(0,0,0,0.5)' : 'rgba(0,0,0,0)';
container = createElement("div", {
class: "popup-box-container",
display: "flex",
id: id,
backgroundColor: 'rgba(0,0,0,0.5)',
backgroundColor: backgroundColor,
clickListener: onClick,
});
gameContainer.appendChild(container);
}
ReactDOM.render(<Popup content={rootComponent} id={id} props={props} />, container);
if(deepestPopupId === "") deepestPopupId = id;
ReactDOM.render(<Popup content={rootComponent} id={id} props={props} removePopup={removePopup} />, container);
return container;
}
@ -67,4 +75,5 @@ export function removePopup(id: string): void {
removeElementById(id);
removeElementById(`${id}-close`);
if(id === deepestPopupId) deepestPopupId = "";
}

@ -1,2 +0,0 @@
export function dialogBoxCreate(txt: string | JSX.Element, preformatted?: boolean): void;
export let dialogBoxOpened: boolean;

@ -1,76 +0,0 @@
import { KEY } from "./helpers/keyCodes";
import { DialogBox } from "./ui/DialogBox";
import React from "react";
import ReactDOM from "react-dom";
/**
* Create and display a pop-up dialog box.
* This dialog box does not allow for any interaction and should close when clicking
* outside of it
*/
let dialogBoxes = [];
// Close dialog box when clicking outside
$(document).click(function(event) {
if (dialogBoxOpened && dialogBoxes.length >= 1) {
if (!$(event.target).closest(dialogBoxes[0]).length){
closeTopmostDialogBox();
}
}
});
function closeTopmostDialogBox() {
if (!dialogBoxOpened || dialogBoxes.length === 0) return;
dialogBoxes[0].remove();
dialogBoxes.shift();
if (dialogBoxes.length == 0) {
dialogBoxOpened = false;
} else {
dialogBoxes[0].style.visibility = "visible";
}
}
// Dialog box close buttons
$(document).on('click', '.dialog-box-close-button', function() {
closeTopmostDialogBox();
});
document.addEventListener("keydown", function (event) {
if (event.keyCode == KEY.ESC && dialogBoxOpened) {
closeTopmostDialogBox();
event.preventDefault();
}
});
let dialogBoxOpened = false;
function dialogBoxCreate(txt, preformatted=false) {
const container = document.createElement("div");
container.setAttribute("class", "dialog-box-container");
let elem = txt;
if (typeof txt === 'string') {
if (preformatted) {
// For text files as they are often computed data that
// shouldn't be wrapped and should retain tabstops.
elem = <pre dangerouslySetInnerHTML={{ __html: txt }} />
} else {
elem = <p dangerouslySetInnerHTML={{ __html: txt.replace(/(?:\r\n|\r|\n)/g, '<br />') }} />
}
}
ReactDOM.render(DialogBox(elem), container);
document.body.appendChild(container);
if (dialogBoxes.length >= 1) {
container.style.visibility = "hidden";
}
dialogBoxes.push(container);
setTimeout(function() {
dialogBoxOpened = true;
}, 400);
}
export {dialogBoxCreate, dialogBoxOpened};

35
utils/DialogBox.tsx Normal file

@ -0,0 +1,35 @@
import { createPopup } from "../src/ui/React/createPopup";
import { getRandomInt } from "./helpers/getRandomInt";
import React from "react";
interface IProps {
content: JSX.Element;
}
export function MessagePopup(props: IProps): React.ReactElement {
return (<>{props.content}</>);
}
function dialogBoxCreate(txt: string | JSX.Element, preformatted = false): void {
const popupId = `popup-`+(Array.from(Array(16))).map(() => `${getRandomInt(0, 9)}`).join('');
if (typeof txt === 'string') {
if (preformatted) {
// For text files as they are often computed data that
// shouldn't be wrapped and should retain tabstops.
createPopup(popupId, MessagePopup, {
content: (<pre dangerouslySetInnerHTML={{ __html: txt }} />),
});
} else {
createPopup(popupId, MessagePopup, {
content: (<p dangerouslySetInnerHTML={{ __html: txt.replace(/(?:\r\n|\r|\n)/g, '<br />') }} />),
});
}
} else {
createPopup(popupId, MessagePopup, {
content: txt,
});
}
}
export {dialogBoxCreate};

@ -76,7 +76,7 @@ function containsAllStrings(arr: string[]): boolean {
}
// Formats a number with commas and a specific number of decimal digits
function formatNumber(num: number, numFractionDigits: number): string {
function formatNumber(num: number, numFractionDigits = 0): string {
return num.toLocaleString(undefined, {
maximumFractionDigits: numFractionDigits,
minimumFractionDigits: numFractionDigits,