Converting bladeburner to react

This commit is contained in:
Olivier Gagnon 2021-06-18 03:38:17 -04:00
parent 78cd319c21
commit 0e9d7450c9
10 changed files with 544 additions and 488 deletions

@ -52,6 +52,11 @@ import { createPopup } from "../utils/uiHelpers/createPopup";
import { removeElement } from "../utils/uiHelpers/removeElement";
import { removeElementById } from "../utils/uiHelpers/removeElementById";
import { SkillElem } from "./Bladeburner/ui/SkillElem";
import { BlackOpElem } from "./Bladeburner/ui/BlackOpElem";
import { OperationElem } from "./Bladeburner/ui/OperationElem";
import { ContractElem } from "./Bladeburner/ui/ContractElem";
import { GeneralActionElem } from "./Bladeburner/ui/GeneralActionElem";
import { StatsTable } from "./ui/React/StatsTable";
import { CopyableText } from "./ui/React/CopyableText";
import { Money } from "./ui/React/Money";
@ -1894,442 +1899,28 @@ Bladeburner.prototype.updateActionAndSkillsContent = function() {
}
Bladeburner.prototype.updateGeneralActionsUIElement = function(el, action) {
removeChildrenFromElement(el);
var isActive = el.classList.contains(ActiveActionCssClass);
var computedActionTimeCurrent = Math.min(this.actionTimeCurrent+this.actionTimeOverflow,this.actionTimeToComplete);
el.appendChild(createElement("h2", { // Header
innerText:isActive ? action.name + " (IN PROGRESS - " +
formatNumber(computedActionTimeCurrent, 0) + " / " +
formatNumber(this.actionTimeToComplete, 0) + ")"
: action.name,
display:"inline-block",
}));
if (isActive) { // Progress bar if its active
var progress = computedActionTimeCurrent / this.actionTimeToComplete;
el.appendChild(createElement("p", {
display:"block",
innerText:createProgressBarText({progress:progress}),
}));
} else {
// Start button
el.appendChild(createElement("a", {
innerText:"Start", class: "a-link-button",
margin:"3px", padding:"3px",
clickListener:() => {
this.action.type = ActionTypes[action.name];
this.action.name = action.name;
this.startAction(this.action);
this.updateActionAndSkillsContent();
return false;
},
}));
}
appendLineBreaks(el, 2);
el.appendChild(createElement("pre", { // Info
innerHTML:action.desc, display:"inline-block",
}));
ReactDOM.unmountComponentAtNode(el);
ReactDOM.render(<GeneralActionElem bladeburner={this} action={action} />, el);
}
Bladeburner.prototype.updateContractsUIElement = function(el, action) {
removeChildrenFromElement(el);
var isActive = el.classList.contains(ActiveActionCssClass);
var estimatedSuccessChance = action.getSuccessChance(this, {est:true});
var computedActionTimeCurrent = Math.min(this.actionTimeCurrent+this.actionTimeOverflow,this.actionTimeToComplete);
el.appendChild(createElement("h2", { // Header
innerText:isActive ? action.name + " (IN PROGRESS - " +
formatNumber(computedActionTimeCurrent, 0) + " / " +
formatNumber(this.actionTimeToComplete, 0) + ")"
: action.name,
display:"inline-block",
}));
if (isActive) { // Progress bar if its active
var progress = computedActionTimeCurrent / this.actionTimeToComplete;
el.appendChild(createElement("p", {
display:"block",
innerText:createProgressBarText({progress:progress}),
}));
} else { // Start button
el.appendChild(createElement("a", {
innerText:"Start", class: "a-link-button",
padding:"3px", margin:"3px",
clickListener:() => {
this.action.type = ActionTypes.Contract;
this.action.name = action.name;
this.startAction(this.action);
this.updateActionAndSkillsContent();
return false;
},
}));
}
// Level and buttons to change level
var maxLevel = (action.level >= action.maxLevel);
appendLineBreaks(el, 2);
el.appendChild(createElement("pre", {
display:"inline-block",
innerText:"Level: " + action.level + " / " + action.maxLevel,
tooltip:action.getSuccessesNeededForNextLevel(BladeburnerConstants.ContractSuccessesPerLevel) + " successes " +
"needed for next level",
}));
el.appendChild(createElement("a", {
class: maxLevel ? "a-link-button-inactive" : "a-link-button", innerHTML:"&uarr;",
padding:"2px", margin:"2px",
tooltip: isActive ? "WARNING: changing the level will restart the contract" : "",
display:"inline",
clickListener:() => {
++action.level;
if (isActive) {this.startAction(this.action);} // Restart Action
this.updateContractsUIElement(el, action);
return false;
},
}));
el.appendChild(createElement("a", {
class: (action.level <= 1) ? "a-link-button-inactive" : "a-link-button", innerHTML:"&darr;",
padding:"2px", margin:"2px",
tooltip: isActive ? "WARNING: changing the level will restart the contract" : "",
display:"inline",
clickListener:() => {
--action.level;
if (isActive) {this.startAction(this.action);} // Restart Action
this.updateContractsUIElement(el, action);
return false;
},
}));
var actionTime = action.getActionTime(this);
appendLineBreaks(el, 2);
el.appendChild(createElement("pre", { // Info
display:"inline-block",
innerHTML:action.desc + "\n\n" +
`Estimated success chance: ${formatNumber(estimatedSuccessChance*100, 1)}% ${action.isStealth?stealthIcon:''}${action.isKill?killIcon:''}\n` +
"Time Required: " + convertTimeMsToTimeElapsedString(actionTime*1000) + "\n" +
"Contracts remaining: " + Math.floor(action.count) + "\n" +
"Successes: " + action.successes + "\n" +
"Failures: " + action.failures,
}));
// 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);
ReactDOM.unmountComponentAtNode(el);
ReactDOM.render(<ContractElem bladeburner={this} action={action} />, el);
}
Bladeburner.prototype.updateOperationsUIElement = function(el, action) {
removeChildrenFromElement(el);
var isActive = el.classList.contains(ActiveActionCssClass);
var estimatedSuccessChance = action.getSuccessChance(this, {est:true});
var computedActionTimeCurrent = Math.min(this.actionTimeCurrent+this.actionTimeOverflow,this.actionTimeToComplete);
el.appendChild(createElement("h2", { // Header
innerText:isActive ? action.name + " (IN PROGRESS - " +
formatNumber(computedActionTimeCurrent, 0) + " / " +
formatNumber(this.actionTimeToComplete, 0) + ")"
: action.name,
display:"inline-block",
}));
if (isActive) { // Progress bar if its active
var progress = computedActionTimeCurrent / this.actionTimeToComplete;
el.appendChild(createElement("p", {
display:"block",
innerText:createProgressBarText({progress:progress}),
}));
} else { // Start button and set Team Size button
el.appendChild(createElement("a", {
innerText:"Start", class: "a-link-button",
margin:"3px", padding:"3px",
clickListener:() => {
this.action.type = ActionTypes.Operation;
this.action.name = action.name;
this.startAction(this.action);
this.updateActionAndSkillsContent();
return false;
},
}));
el.appendChild(createElement("a", {
innerText:"Set Team Size (Curr Size: " + formatNumber(action.teamCount, 0) + ")", class:"a-link-button",
margin:"3px", padding:"3px",
clickListener:() => {
var popupId = "bladeburner-operation-set-team-size-popup";
var txt = createElement("p", {
innerText:"Enter the amount of team members you would like to take on these " +
"operations. 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.",
});
var input = createElement("input", {
type:"number", placeholder: "Team size", class: "text-input",
});
var setBtn = createElement("a", {
innerText:"Confirm", class:"a-link-button",
clickListener:() => {
var num = Math.round(parseFloat(input.value));
if (isNaN(num) || num < 0) {
dialogBoxCreate("Invalid value entered for number of Team Members (must be numeric, positive)")
} else {
action.teamCount = num;
this.updateOperationsUIElement(el, action);
}
removeElementById(popupId);
return false;
},
});
var cancelBtn = createElement("a", {
innerText:"Cancel", class:"a-link-button",
clickListener:() => {
removeElementById(popupId);
return false;
},
});
createPopup(popupId, [txt, input, setBtn, cancelBtn]);
input.focus();
},
}));
}
// Level and buttons to change level
var maxLevel = (action.level >= action.maxLevel);
appendLineBreaks(el, 2);
el.appendChild(createElement("pre", {
display:"inline-block",
innerText:"Level: " + action.level + " / " + action.maxLevel,
tooltip:action.getSuccessesNeededForNextLevel(BladeburnerConstants.OperationSuccessesPerLevel) + " successes " +
"needed for next level",
}));
el.appendChild(createElement("a", {
class: maxLevel ? "a-link-button-inactive" : "a-link-button", innerHTML:"&uarr;",
padding:"2px", margin:"2px",
tooltip: isActive ? "WARNING: changing the level will restart the Operation" : "",
display:"inline",
clickListener:() => {
++action.level;
if (isActive) {this.startAction(this.action);} // Restart Action
this.updateOperationsUIElement(el, action);
return false;
},
}));
el.appendChild(createElement("a", {
class: (action.level <= 1) ? "a-link-button-inactive" : "a-link-button", innerHTML:"&darr;",
padding:"2px", margin:"2px",
tooltip: isActive ? "WARNING: changing the level will restart the Operation" : "",
display:"inline",
clickListener:() => {
--action.level;
if (isActive) {this.startAction(this.action);} // Restart Action
this.updateOperationsUIElement(el, action);
return false;
},
}));
// General Info
var actionTime = action.getActionTime(this);
appendLineBreaks(el, 2);
el.appendChild(createElement("pre", {
display:"inline-block",
innerHTML:action.desc + "\n\n" +
`Estimated success chance: ${formatNumber(estimatedSuccessChance*100, 1)}% ${action.isStealth?stealthIcon:''}${action.isKill?killIcon:''}\n` +
"Time Required: " + convertTimeMsToTimeElapsedString(actionTime*1000) + "\n" +
"Operations remaining: " + Math.floor(action.count) + "\n" +
"Successes: " + action.successes + "\n" +
"Failures: " + action.failures,
}));
// 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 operation level when possible",
}));
const checkboxInput = createElement("input", {
type:"checkbox",
id: autolevelCheckboxId,
checked: action.autoLevel,
changeListener: () => {
action.autoLevel = checkboxInput.checked;
},
});
el.appendChild(checkboxInput);
ReactDOM.unmountComponentAtNode(el);
ReactDOM.render(<OperationElem bladeburner={this} action={action} />, el);
}
Bladeburner.prototype.updateBlackOpsUIElement = function(el, action) {
removeChildrenFromElement(el);
var isActive = el.classList.contains(ActiveActionCssClass);
var isCompleted = (this.blackops[action.name] != null);
var estimatedSuccessChance = action.getSuccessChance(this, {est:true});
var actionTime = action.getActionTime(this);
var hasReqdRank = this.rank >= action.reqdRank;
var computedActionTimeCurrent = Math.min(this.actionTimeCurrent+this.actionTimeOverflow,this.actionTimeToComplete);
// UI for Completed Black Op
if (isCompleted) {
el.appendChild(createElement("h2", {
innerText:action.name + " (COMPLETED)", display:"block",
}));
return;
}
el.appendChild(createElement("h2", { // Header
innerText:isActive ? action.name + " (IN PROGRESS - " +
formatNumber(computedActionTimeCurrent, 0) + " / " +
formatNumber(this.actionTimeToComplete, 0) + ")"
: action.name,
display:"inline-block",
}));
if (isActive) { // Progress bar if its active
var progress = computedActionTimeCurrent / this.actionTimeToComplete;
el.appendChild(createElement("p", {
display:"block",
innerText:createProgressBarText({progress:progress}),
}));
} else {
el.appendChild(createElement("a", { // Start button
innerText:"Start", margin:"3px", padding:"3px",
class:hasReqdRank ? "a-link-button" : "a-link-button-inactive",
clickListener:() => {
this.action.type = ActionTypes.BlackOperation;
this.action.name = action.name;
this.startAction(this.action);
this.updateActionAndSkillsContent();
return false;
},
}));
el.appendChild(createElement("a", { // Set Team Size Button
innerText:"Set Team Size (Curr Size: " + formatNumber(action.teamCount, 0) + ")", class:"a-link-button",
margin:"3px", padding:"3px",
clickListener:() => {
var popupId = "bladeburner-operation-set-team-size-popup";
var txt = createElement("p", {
innerText:"Enter the amount of team members you would like to take on this " +
"BlackOp. 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.",
});
var input = createElement("input", {
type:"number", placeholder: "Team size", class: "text-input",
});
var setBtn = createElement("a", {
innerText:"Confirm", class:"a-link-button",
clickListener:() => {
var num = Math.round(parseFloat(input.value));
if (isNaN(num) || num < 0) {
dialogBoxCreate("Invalid value entered for number of Team Members (must be numeric, positive)")
} else {
action.teamCount = num;
this.updateBlackOpsUIElement(el, action);
}
removeElementById(popupId);
return false;
},
});
var cancelBtn = createElement("a", {
innerText:"Cancel", class:"a-link-button",
clickListener:() => {
removeElementById(popupId);
return false;
},
});
createPopup(popupId, [txt, input, setBtn, cancelBtn]);
input.focus();
},
}));
}
// Info
appendLineBreaks(el, 2);
el.appendChild(createElement("p", {
display:"inline-block",
innerHTML:"<br>" + action.desc + "<br><br>",
}));
el.appendChild(createElement("p", {
display:"block", color:hasReqdRank ? "white" : "red",
innerHTML:"Required Rank: " + formatNumber(action.reqdRank, 0) + "<br>",
}));
el.appendChild(createElement("p", {
display:"inline-block",
innerHTML:`Estimated Success Chance: ${formatNumber(estimatedSuccessChance*100, 1)}% ${action.isStealth?stealthIcon:''}${action.isKill?killIcon:''}\n` +
"Time Required: " + convertTimeMsToTimeElapsedString(actionTime*1000),
}))
ReactDOM.unmountComponentAtNode(el);
ReactDOM.render(<BlackOpElem bladeburner={this} action={action} />, el);
}
Bladeburner.prototype.updateSkillsUIElement = function(el, skill) {
removeChildrenFromElement(el);
var skillName = skill.name;
var currentLevel = 0;
if (this.skills[skillName] && !isNaN(this.skills[skillName])) {
currentLevel = this.skills[skillName];
}
var pointCost = skill.calculateCost(currentLevel);
const nameDiv = createElement("div");
ReactDOM.render(React.createElement(CopyableText, {value: skill.name}, null), nameDiv);
el.appendChild(nameDiv)
const h2 = createElement("h2", { // Header
display:"inline-block",
});
h2.appendChild(nameDiv);
el.appendChild(h2);
var canLevel = this.skillPoints >= pointCost;
var maxLvl = skill.maxLvl ? currentLevel >= skill.maxLvl : false;
el.appendChild(createElement("a", { // Level up button
innerText:"Level", display:"inline-block",
class: canLevel && !maxLvl ? "a-link-button" : "a-link-button-inactive",
margin:"3px", padding:"3px",
clickListener:() => {
if (this.skillPoints < pointCost) {return;}
this.skillPoints -= pointCost;
this.upgradeSkill(skill);
this.createActionAndSkillsContent();
return false;
},
}));
appendLineBreaks(el, 2);
el.appendChild(createElement("p", {
display:"block",
innerText:`Level: ${currentLevel}`,
}));
if (maxLvl) {
el.appendChild(createElement("p", {
color:"red", display:"block",
innerText:"MAX LEVEL",
}));
} else {
el.appendChild(createElement("p", {
display:"block",
innerText:"Skill Points required: " + formatNumber(pointCost, 0),
}));
}
el.appendChild(createElement("p", { // Info/Description
innerHTML:skill.desc, display:"inline-block",
}));
ReactDOM.unmountComponentAtNode(el);
ReactDOM.render(<SkillElem bladeburner={this} skill={skill} />, el);
}
// Bladeburner Console Window

@ -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,
});
};

@ -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,110 @@
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";
interface IProps {
bladeburner: any;
action: any;
}
export function BlackOpElem(props: IProps): React.ReactElement {
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.bladeburner.action);
props.bladeburner.updateActionAndSkillsContent();
}
function onTeam() {
// TODO(hydroflame): this needs some changes that are in the Gang conversion.
// var popupId = "bladeburner-operation-set-team-size-popup";
// var txt = createElement("p", {
// innerText:"Enter the amount of team members you would like to take on this " +
// "BlackOp. 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.",
// });
// var input = createElement("input", {
// type:"number", placeholder: "Team size", class: "text-input",
// });
// var setBtn = createElement("a", {
// innerText:"Confirm", class:"a-link-button",
// clickListener:() => {
// var num = Math.round(parseFloat(input.value));
// if (isNaN(num) || num < 0) {
// dialogBoxCreate("Invalid value entered for number of Team Members (must be numeric, positive)")
// } else {
// action.teamCount = num;
// this.updateBlackOpsUIElement(el, action);
// }
// removeElementById(popupId);
// return false;
// },
// });
// var cancelBtn = createElement("a", {
// innerText:"Cancel", class:"a-link-button",
// clickListener:() => {
// removeElementById(popupId);
// return false;
// },
// });
// createPopup(popupId, [txt, input, setBtn, cancelBtn]);
// input.focus();
}
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>
</>);
}

@ -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,137 @@
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";
interface IProps {
bladeburner: any;
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.bladeburner.action);
props.bladeburner.updateActionAndSkillsContent();
setRerender(old => !old);
}
function increaseLevel() {
++props.action.level;
if (isActive) props.bladeburner.startAction(props.bladeburner.action);
setRerender(old => !old);
}
function decreaseLevel() {
--props.action.level;
if (isActive) props.bladeburner.startAction(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);
*/

@ -0,0 +1,49 @@
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";
interface IProps {
bladeburner: any;
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.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,157 @@
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";
interface IProps {
bladeburner: any;
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.bladeburner.action);
props.bladeburner.updateActionAndSkillsContent();
setRerender(old => !old);
}
function onTeam() {
// var popupId = "bladeburner-operation-set-team-size-popup";
// var txt = createElement("p", {
// innerText:"Enter the amount of team members you would like to take on these " +
// "operations. 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.",
// });
// var input = createElement("input", {
// type:"number", placeholder: "Team size", class: "text-input",
// });
// var setBtn = createElement("a", {
// innerText:"Confirm", class:"a-link-button",
// clickListener:() => {
// var num = Math.round(parseFloat(input.value));
// if (isNaN(num) || num < 0) {
// dialogBoxCreate("Invalid value entered for number of Team Members (must be numeric, positive)")
// } else {
// action.teamCount = num;
// this.updateOperationsUIElement(el, action);
// }
// removeElementById(popupId);
// return false;
// },
// });
// var cancelBtn = createElement("a", {
// innerText:"Cancel", class:"a-link-button",
// clickListener:() => {
// removeElementById(popupId);
// return false;
// },
// });
// createPopup(popupId, [txt, input, setBtn, cancelBtn]);
// input.focus();
}
function increaseLevel() {
++props.action.level;
if (isActive) props.bladeburner.startAction(props.bladeburner.action);
setRerender(old => !old);
}
function decreaseLevel() {
--props.action.level;
if (isActive) props.bladeburner.startAction(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}/>
</>);
}

@ -0,0 +1,46 @@
import * as React from "react";
import { CopyableText } from "../../ui/React/CopyableText";
import { formatNumber } from "../../../utils/StringHelperFunctions";
interface IProps {
skill: any;
bladeburner: any;
}
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.bladeburner.createActionAndSkillsContent();
}
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}} />
</>);
}

@ -475,6 +475,7 @@ const Engine = {
Engine.hideAllContent();
routing.navigateTo(Page.Bladeburner);
Player.bladeburner.createContent();
MainMenuLinks.Bladeburner.classList.add("active");
} catch(e) {
exceptionAlert(e);
}