mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2024-12-22 14:12:27 +01:00
commit
1fbb971d6f
@ -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) => {
|
||||
// look, I don't know how to make a "smart decimals"
|
||||
// todo, make it smarter
|
||||
if(x === 1.0777-1) return "7.77%";
|
||||
|
3265
src/Bladeburner.jsx
3265
src/Bladeburner.jsx
File diff suppressed because it is too large
Load Diff
28
src/Bladeburner/ActionIdentifier.ts
Normal file
28
src/Bladeburner/ActionIdentifier.ts
Normal file
@ -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: string = "";
|
||||
type: number = -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;
|
1986
src/Bladeburner/Bladeburner.ts
Normal file
1986
src/Bladeburner/Bladeburner.ts
Normal file
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[];
|
||||
|
14
src/Bladeburner/data/Icons.tsx
Normal file
14
src/Bladeburner/data/Icons.tsx
Normal file
@ -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>
|
49
src/Bladeburner/ui/AllPages.tsx
Normal file
49
src/Bladeburner/ui/AllPages.tsx
Normal file
@ -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>
|
||||
</>);
|
||||
}
|
87
src/Bladeburner/ui/BlackOpElem.tsx
Normal file
87
src/Bladeburner/ui/BlackOpElem.tsx
Normal file
@ -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() {
|
||||
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() {
|
||||
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>
|
||||
</>);
|
||||
}
|
43
src/Bladeburner/ui/BlackOpList.tsx
Normal file
43
src/Bladeburner/ui/BlackOpList.tsx
Normal file
@ -0,0 +1,43 @@
|
||||
import * as React 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 { 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>,
|
||||
)}
|
||||
</>);
|
||||
}
|
28
src/Bladeburner/ui/BlackOpPage.tsx
Normal file
28
src/Bladeburner/ui/BlackOpPage.tsx
Normal file
@ -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>)
|
||||
}
|
115
src/Bladeburner/ui/Console.tsx
Normal file
115
src/Bladeburner/ui/Console.tsx
Normal file
@ -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() {
|
||||
if(lastRef.current)
|
||||
lastRef.current.scrollTop = lastRef.current.scrollHeight;
|
||||
}
|
||||
|
||||
function rerender() {
|
||||
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>);
|
||||
}
|
139
src/Bladeburner/ui/ContractElem.tsx
Normal file
139
src/Bladeburner/ui/ContractElem.tsx
Normal file
@ -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() {
|
||||
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() {
|
||||
++props.action.level;
|
||||
if (isActive) props.bladeburner.startAction(props.player, props.bladeburner.action);
|
||||
setRerender(old => !old);
|
||||
}
|
||||
|
||||
function decreaseLevel() {
|
||||
--props.action.level;
|
||||
if (isActive) props.bladeburner.startAction(props.player, props.bladeburner.action);
|
||||
setRerender(old => !old);
|
||||
}
|
||||
|
||||
function onAutolevel(event: React.ChangeEvent<HTMLInputElement>) {
|
||||
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);
|
||||
|
||||
*/
|
25
src/Bladeburner/ui/ContractList.tsx
Normal file
25
src/Bladeburner/ui/ContractList.tsx
Normal file
@ -0,0 +1,25 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import {
|
||||
formatNumber,
|
||||
convertTimeMsToTimeElapsedString,
|
||||
} from "../../../utils/StringHelperFunctions";
|
||||
import { ContractElem } from "./ContractElem";
|
||||
import { Contract } from "../Contract";
|
||||
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>,
|
||||
)}
|
||||
</>);
|
||||
}
|
23
src/Bladeburner/ui/ContractPage.tsx
Normal file
23
src/Bladeburner/ui/ContractPage.tsx
Normal file
@ -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} />
|
||||
</>);
|
||||
}
|
52
src/Bladeburner/ui/GeneralActionElem.tsx
Normal file
52
src/Bladeburner/ui/GeneralActionElem.tsx
Normal file
@ -0,0 +1,52 @@
|
||||
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 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() {
|
||||
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>
|
||||
</>);
|
||||
}
|
30
src/Bladeburner/ui/GeneralActionList.tsx
Normal file
30
src/Bladeburner/ui/GeneralActionList.tsx
Normal file
@ -0,0 +1,30 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import {
|
||||
formatNumber,
|
||||
convertTimeMsToTimeElapsedString,
|
||||
} from "../../../utils/StringHelperFunctions";
|
||||
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>,
|
||||
)}
|
||||
</>);
|
||||
}
|
19
src/Bladeburner/ui/GeneralActionPage.tsx
Normal file
19
src/Bladeburner/ui/GeneralActionPage.tsx
Normal file
@ -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} />
|
||||
</>);
|
||||
}
|
133
src/Bladeburner/ui/OperationElem.tsx
Normal file
133
src/Bladeburner/ui/OperationElem.tsx
Normal file
@ -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() {
|
||||
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() {
|
||||
const popupId = "bladeburner-operation-set-team-size-popup";
|
||||
createPopup(popupId, TeamSizePopup, {
|
||||
bladeburner: props.bladeburner,
|
||||
action: props.action,
|
||||
popupId: popupId,
|
||||
});
|
||||
}
|
||||
|
||||
function increaseLevel() {
|
||||
++props.action.level;
|
||||
if (isActive) props.bladeburner.startAction(props.player, props.bladeburner.action);
|
||||
setRerender(old => !old);
|
||||
}
|
||||
|
||||
function decreaseLevel() {
|
||||
--props.action.level;
|
||||
if (isActive) props.bladeburner.startAction(props.player, props.bladeburner.action);
|
||||
setRerender(old => !old);
|
||||
}
|
||||
|
||||
function onAutolevel(event: React.ChangeEvent<HTMLInputElement>) {
|
||||
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}/>
|
||||
</>);
|
||||
}
|
25
src/Bladeburner/ui/OperationList.tsx
Normal file
25
src/Bladeburner/ui/OperationList.tsx
Normal file
@ -0,0 +1,25 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import {
|
||||
formatNumber,
|
||||
convertTimeMsToTimeElapsedString,
|
||||
} from "../../../utils/StringHelperFunctions";
|
||||
import { OperationElem } from "./OperationElem";
|
||||
import { Operation } from "../Operation";
|
||||
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>,
|
||||
)}
|
||||
</>);
|
||||
}
|
34
src/Bladeburner/ui/OperationPage.tsx
Normal file
34
src/Bladeburner/ui/OperationPage.tsx
Normal file
@ -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} />
|
||||
</>);
|
||||
}
|
28
src/Bladeburner/ui/Root.tsx
Normal file
28
src/Bladeburner/ui/Root.tsx
Normal file
@ -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>);
|
||||
}
|
48
src/Bladeburner/ui/SkillElem.tsx
Normal file
48
src/Bladeburner/ui/SkillElem.tsx
Normal file
@ -0,0 +1,48 @@
|
||||
import React, { useState } 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() {
|
||||
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}} />
|
||||
</>);
|
||||
}
|
18
src/Bladeburner/ui/SkillList.tsx
Normal file
18
src/Bladeburner/ui/SkillList.tsx
Normal file
@ -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>,
|
||||
)}
|
||||
</>);
|
||||
}
|
70
src/Bladeburner/ui/SkillPage.tsx
Normal file
70
src/Bladeburner/ui/SkillPage.tsx
Normal file
@ -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) {
|
||||
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]) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
139
src/Bladeburner/ui/Stats.tsx
Normal file
139
src/Bladeburner/ui/Stats.tsx
Normal file
@ -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() {
|
||||
const popupId = "bladeburner-travel-popup";
|
||||
createPopup(popupId, TravelPopup, {
|
||||
bladeburner: props.bladeburner,
|
||||
popupId: popupId,
|
||||
});
|
||||
}
|
||||
|
||||
function openFaction() {
|
||||
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 />
|
||||
</>);
|
||||
}
|
38
src/Bladeburner/ui/TeamSizePopup.tsx
Normal file
38
src/Bladeburner/ui/TeamSizePopup.tsx
Normal file
@ -0,0 +1,38 @@
|
||||
import React, { useState } from "react";
|
||||
import { removePopup } from "../../ui/React/createPopup";
|
||||
import { BladeburnerConstants } from "../data/Constants";
|
||||
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>
|
||||
</>);
|
||||
}
|
32
src/Bladeburner/ui/TravelPopup.tsx
Normal file
32
src/Bladeburner/ui/TravelPopup.tsx
Normal file
@ -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) {
|
||||
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
|
||||
<div key={city} className="cmpy-mgmt-find-employee-option"
|
||||
onClick={() => travel(city)}>
|
||||
{city}
|
||||
</div>)}
|
||||
</>);
|
||||
}
|
@ -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"
|
||||
|
@ -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>
|
||||
</>);
|
||||
}
|
@ -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
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;
|
@ -17,7 +17,8 @@ import { AugmentationNames } from "./Augmentation/data/AugmentationNames";
|
||||
import {
|
||||
initBitNodeMultipliers,
|
||||
} from "./BitNode/BitNode";
|
||||
import { Bladeburner } from "./Bladeburner";
|
||||
import { Bladeburner } from "./Bladeburner/Bladeburner";
|
||||
import { process as ProcessBladeburner } from "./Bladeburner/Bladeburner";
|
||||
import { CharacterOverviewComponent } from "./ui/React/CharacterOverview";
|
||||
import { cinematicTextFlag } from "./CinematicText";
|
||||
import { generateRandomContract } from "./CodingContractGenerator";
|
||||
@ -36,6 +37,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,
|
||||
@ -229,6 +231,7 @@ const Engine = {
|
||||
infiltrationContent: null,
|
||||
stockMarketContent: null,
|
||||
gangContent: null,
|
||||
bladeburnerContent: null,
|
||||
locationContent: null,
|
||||
workInProgressContent: null,
|
||||
redPillContent: null,
|
||||
@ -470,15 +473,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 +537,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 +553,6 @@ const Engine = {
|
||||
Player.corporation.clearUI();
|
||||
}
|
||||
|
||||
if (Player.bladeburner instanceof Bladeburner) {
|
||||
Player.bladeburner.clearContent();
|
||||
}
|
||||
|
||||
clearResleevesPage();
|
||||
clearSleevesPage();
|
||||
|
||||
@ -882,7 +884,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 +1261,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>
|
||||
|
@ -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 /></>;
|
||||
|
@ -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,
|
||||
|
Loading…
Reference in New Issue
Block a user