From 0e9d7450c9eb4ce74fca77cc5c08ec07b1bd18d0 Mon Sep 17 00:00:00 2001 From: Olivier Gagnon Date: Fri, 18 Jun 2021 03:38:17 -0400 Subject: [PATCH 01/15] Converting bladeburner to react --- src/Bladeburner.jsx | 439 +-------------------- src/Bladeburner/data/ActionTypes.ts | 17 +- src/Bladeburner/data/Icons.tsx | 14 + src/Bladeburner/ui/BlackOpElem.tsx | 110 ++++++ src/Bladeburner/ui/BlackOperationsPage.tsx | 62 --- src/Bladeburner/ui/ContractElem.tsx | 137 +++++++ src/Bladeburner/ui/GeneralActionElem.tsx | 49 +++ src/Bladeburner/ui/OperationElem.tsx | 157 ++++++++ src/Bladeburner/ui/SkillElem.tsx | 46 +++ src/engine.jsx | 1 + 10 files changed, 544 insertions(+), 488 deletions(-) create mode 100644 src/Bladeburner/data/Icons.tsx create mode 100644 src/Bladeburner/ui/BlackOpElem.tsx delete mode 100644 src/Bladeburner/ui/BlackOperationsPage.tsx create mode 100644 src/Bladeburner/ui/ContractElem.tsx create mode 100644 src/Bladeburner/ui/GeneralActionElem.tsx create mode 100644 src/Bladeburner/ui/OperationElem.tsx create mode 100644 src/Bladeburner/ui/SkillElem.tsx diff --git a/src/Bladeburner.jsx b/src/Bladeburner.jsx index 2a3316fd1..93d48e668 100644 --- a/src/Bladeburner.jsx +++ b/src/Bladeburner.jsx @@ -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(, 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:"↑", - 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:"↓", - 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(, 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:"↑", - 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:"↓", - 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(, 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:"
" + action.desc + "

", - })); - el.appendChild(createElement("p", { - display:"block", color:hasReqdRank ? "white" : "red", - innerHTML:"Required Rank: " + formatNumber(action.reqdRank, 0) + "
", - })); - 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(, 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(, el); } // Bladeburner Console Window diff --git a/src/Bladeburner/data/ActionTypes.ts b/src/Bladeburner/data/ActionTypes.ts index f4392313c..a1252b1e4 100644 --- a/src/Bladeburner/data/ActionTypes.ts +++ b/src/Bladeburner/data/ActionTypes.ts @@ -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, -}); \ No newline at end of file +}; \ No newline at end of file diff --git a/src/Bladeburner/data/Icons.tsx b/src/Bladeburner/data/Icons.tsx new file mode 100644 index 000000000..296c1d249 --- /dev/null +++ b/src/Bladeburner/data/Icons.tsx @@ -0,0 +1,14 @@ +import * as React from "react"; + +export const stealthIcon = + + + + + +export const killIcon = + + + + + \ No newline at end of file diff --git a/src/Bladeburner/ui/BlackOpElem.tsx b/src/Bladeburner/ui/BlackOpElem.tsx new file mode 100644 index 000000000..bcdd65855 --- /dev/null +++ b/src/Bladeburner/ui/BlackOpElem.tsx @@ -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 ( +

{props.action.name} (COMPLETED)

); + } + + 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 (<> +

+ {isActive ? + <>{props.action.name} (IN PROGRESS - {formatNumber(computedActionTimeCurrent, 0)} / {formatNumber(props.bladeburner.actionTimeToComplete, 0)}) : + <>{props.action.name} + } +

+ {isActive ? +

{createProgressBarText({progress: computedActionTimeCurrent / props.bladeburner.actionTimeToComplete})}

: + <> + Start + + Set Team Size (Curr Size: {formatNumber(props.action.teamCount, 0)}) + + } +
+
+

+
+
+

+ Required Rank: {formatNumber(props.action.reqdRank, 0)} +

+
+

+ Estimated Success Chance: {formatNumber(estimatedSuccessChance*100, 1)}% {props.action.isStealth?stealthIcon:<>}{props.action.isKill?killIcon:<>} +
+ Time Required: {convertTimeMsToTimeElapsedString(actionTime*1000)} +

+ ); +} \ No newline at end of file diff --git a/src/Bladeburner/ui/BlackOperationsPage.tsx b/src/Bladeburner/ui/BlackOperationsPage.tsx deleted file mode 100644 index 7f73397c1..000000000 --- a/src/Bladeburner/ui/BlackOperationsPage.tsx +++ /dev/null @@ -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.

" + - "Your ultimate goal to climb through the ranks of Bladeburners is to complete " + - "all of the Black Ops.

" + - "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 (
-

- Black Operations (Black Ops) are special, one-time covert operations. Each Black Op must be unlocked successively by completing the one before it.

- Your ultimate goal to climb through the ranks of Bladeburners is to complete all of the Black Ops.

- Like normal operations, you may use a team for Black Ops. Failing a black op will incur heavy HP and rank losses.

- {blackops.map(() =>
-
, - )} -
) -} diff --git a/src/Bladeburner/ui/ContractElem.tsx b/src/Bladeburner/ui/ContractElem.tsx new file mode 100644 index 000000000..9ab48bba1 --- /dev/null +++ b/src/Bladeburner/ui/ContractElem.tsx @@ -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) { + props.action.autoLevel = event.target.checked; + setRerender(old => !old); + } + + return (<> +

+ {isActive ? + <>{props.action.name} (IN PROGRESS - {formatNumber(computedActionTimeCurrent, 0)} / {formatNumber(props.bladeburner.actionTimeToComplete, 0)}) : + <>{props.action.name} + } +

+ {isActive ? +

{createProgressBarText({progress:computedActionTimeCurrent / props.bladeburner.actionTimeToComplete})}

: + <> + + Start + + } +
+
+
+            
+                {props.action.getSuccessesNeededForNextLevel(BladeburnerConstants.ContractSuccessesPerLevel)} successes needed for next level
+            
+            Level: {props.action.level} / {props.action.maxLevel}
+        
+ + {isActive && (WARNING: changing the level will restart the Operation)} + ↑ + + + {isActive && (WARNING: changing the level will restart the Operation)} + ↓ + +
+
+
+
+

+Estimated success chance: {formatNumber(estimatedSuccessChance*100, 1)}% {props.action.isStealth?stealthIcon:<>}${props.action.isKill?killIcon:<>}
+Time Required: {convertTimeMsToTimeElapsedString(actionTime*1000)}
+Contracts remaining: {Math.floor(props.action.count)}
+Successes: {props.action.successes}
+Failures: {props.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); + +*/ \ No newline at end of file diff --git a/src/Bladeburner/ui/GeneralActionElem.tsx b/src/Bladeburner/ui/GeneralActionElem.tsx new file mode 100644 index 000000000..93b7b1e2d --- /dev/null +++ b/src/Bladeburner/ui/GeneralActionElem.tsx @@ -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 (<> +

+ {isActive ? + <>{props.action.name} (IN PROGRESS - {formatNumber(computedActionTimeCurrent, 0)} / {formatNumber(props.bladeburner.actionTimeToComplete, 0)}) : + <>{props.action.name} + } +

+ {isActive ? +

{createProgressBarText({progress:computedActionTimeCurrent / props.bladeburner.actionTimeToComplete})}

: + <> + + Start + + } +
+
+

+    );
+}
\ No newline at end of file
diff --git a/src/Bladeburner/ui/OperationElem.tsx b/src/Bladeburner/ui/OperationElem.tsx
new file mode 100644
index 000000000..1dabeb650
--- /dev/null
+++ b/src/Bladeburner/ui/OperationElem.tsx
@@ -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) {
+        props.action.autoLevel = event.target.checked;
+        setRerender(old => !old);
+    }
+
+    return (<>
+        

+ {isActive ? + <>{props.action.name} (IN PROGRESS - {formatNumber(computedActionTimeCurrent, 0)} / {formatNumber(props.bladeburner.actionTimeToComplete, 0)}) : + <>{props.action.name} + } +

+ {isActive ? +

{createProgressBarText({progress:computedActionTimeCurrent / props.bladeburner.actionTimeToComplete})}

: + <> + + Start + + + Set Team Size (Curr Size: {formatNumber(props.action.teamCount, 0)}) + + } +
+
+
+            
+                {props.action.getSuccessesNeededForNextLevel(BladeburnerConstants.OperationSuccessesPerLevel)} successes needed for next level
+            
+            Level: {props.action.level} / {props.action.maxLevel}
+        
+ + {isActive && (WARNING: changing the level will restart the Operation)} + ↑ + + + {isActive && (WARNING: changing the level will restart the Operation)} + ↓ + +
+
+
+
+

+Estimated success chance: {formatNumber(estimatedSuccessChance*100, 1)}% {props.action.isStealth?stealthIcon:<>}{props.action.isKill?killIcon:<>}
+Time Required: {convertTimeMsToTimeElapsedString(actionTime*1000)}
+Operations remaining: {Math.floor(props.action.count)}
+Successes: {props.action.successes}
+Failures: {props.action.failures} +
+
+ + + ); +} diff --git a/src/Bladeburner/ui/SkillElem.tsx b/src/Bladeburner/ui/SkillElem.tsx new file mode 100644 index 000000000..af7ebfbf1 --- /dev/null +++ b/src/Bladeburner/ui/SkillElem.tsx @@ -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 (<> +

+ +

+ + Level + +
+
+

Level: {currentLevel}

+ {maxLvl ? +

MAX LEVEL

: +

Skill Points required: {formatNumber(pointCost, 0)}

} +

+ ); +} \ No newline at end of file diff --git a/src/engine.jsx b/src/engine.jsx index 244dec641..db76bee30 100644 --- a/src/engine.jsx +++ b/src/engine.jsx @@ -475,6 +475,7 @@ const Engine = { Engine.hideAllContent(); routing.navigateTo(Page.Bladeburner); Player.bladeburner.createContent(); + MainMenuLinks.Bladeburner.classList.add("active"); } catch(e) { exceptionAlert(e); } From 988ca3776482771d83575d15ea879320cc20d6c4 Mon Sep 17 00:00:00 2001 From: Olivier Gagnon Date: Fri, 18 Jun 2021 16:22:12 -0400 Subject: [PATCH 02/15] converting more blade to react/ts --- src/Bladeburner.jsx | 374 ++--------------------- src/Bladeburner/ui/BlackOpList.tsx | 42 +++ src/Bladeburner/ui/BlackOpPage.tsx | 25 ++ src/Bladeburner/ui/ContractList.tsx | 30 ++ src/Bladeburner/ui/ContractPage.tsx | 20 ++ src/Bladeburner/ui/GeneralActionList.tsx | 35 +++ src/Bladeburner/ui/GeneralActionPage.tsx | 16 + src/Bladeburner/ui/OperationList.tsx | 30 ++ src/Bladeburner/ui/OperationPage.tsx | 31 ++ src/Bladeburner/ui/SkillList.tsx | 17 ++ src/Bladeburner/ui/SkillPage.tsx | 66 ++++ src/Bladeburner/ui/Stats.tsx | 78 +++++ src/ui/CharacterInfo.tsx | 4 +- src/ui/React/StatsTable.tsx | 2 +- utils/StringHelperFunctions.ts | 2 +- 15 files changed, 421 insertions(+), 351 deletions(-) create mode 100644 src/Bladeburner/ui/BlackOpList.tsx create mode 100644 src/Bladeburner/ui/BlackOpPage.tsx create mode 100644 src/Bladeburner/ui/ContractList.tsx create mode 100644 src/Bladeburner/ui/ContractPage.tsx create mode 100644 src/Bladeburner/ui/GeneralActionList.tsx create mode 100644 src/Bladeburner/ui/GeneralActionPage.tsx create mode 100644 src/Bladeburner/ui/OperationList.tsx create mode 100644 src/Bladeburner/ui/OperationPage.tsx create mode 100644 src/Bladeburner/ui/SkillList.tsx create mode 100644 src/Bladeburner/ui/SkillPage.tsx create mode 100644 src/Bladeburner/ui/Stats.tsx diff --git a/src/Bladeburner.jsx b/src/Bladeburner.jsx index 93d48e668..41f4ddafc 100644 --- a/src/Bladeburner.jsx +++ b/src/Bladeburner.jsx @@ -53,10 +53,22 @@ import { removeElement } from "../utils/uiHelpers/removeElement"; import { removeElementById } from "../utils/uiHelpers/removeElementById"; import { SkillElem } from "./Bladeburner/ui/SkillElem"; +import { SkillList } from "./Bladeburner/ui/SkillList"; import { BlackOpElem } from "./Bladeburner/ui/BlackOpElem"; +import { BlackOpList } from "./Bladeburner/ui/BlackOpList"; import { OperationElem } from "./Bladeburner/ui/OperationElem"; +import { OperationList } from "./Bladeburner/ui/OperationList"; import { ContractElem } from "./Bladeburner/ui/ContractElem"; +import { ContractList } from "./Bladeburner/ui/ContractList"; import { GeneralActionElem } from "./Bladeburner/ui/GeneralActionElem"; +import { GeneralActionList } from "./Bladeburner/ui/GeneralActionList"; +import { GeneralActionPage } from "./Bladeburner/ui/GeneralActionPage"; +import { ContractPage } from "./Bladeburner/ui/ContractPage"; +import { OperationPage } from "./Bladeburner/ui/OperationPage"; +import { BlackOpPage } from "./Bladeburner/ui/BlackOpPage"; +import { SkillPage } from "./Bladeburner/ui/SkillPage"; +import { Stats } from "./Bladeburner/ui/Stats"; + import { StatsTable } from "./ui/React/StatsTable"; import { CopyableText } from "./ui/React/CopyableText"; import { Money } from "./ui/React/Money"; @@ -1554,19 +1566,24 @@ Bladeburner.prototype.createActionAndSkillsContent = function() { switch(currTab) { case "general": - this.createGeneralActionsContent(); + ReactDOM.unmountComponentAtNode(DomElems.actionsAndSkillsDesc); + ReactDOM.render(, DomElems.actionsAndSkillsDesc); break; case "contracts": - this.createContractsContent(); + ReactDOM.unmountComponentAtNode(DomElems.actionsAndSkillsDesc); + ReactDOM.render(, DomElems.actionsAndSkillsDesc); break; case "operations": - this.createOperationsContent(); + ReactDOM.unmountComponentAtNode(DomElems.actionsAndSkillsDesc); + ReactDOM.render(, DomElems.actionsAndSkillsDesc); break; case "blackops": - this.createBlackOpsContent(); + ReactDOM.unmountComponentAtNode(DomElems.actionsAndSkillsDesc); + ReactDOM.render(, DomElems.actionsAndSkillsDesc); break; case "skills": - this.createSkillsContent(); + ReactDOM.unmountComponentAtNode(DomElems.actionsAndSkillsDesc); + ReactDOM.render(, DomElems.actionsAndSkillsDesc); break; default: throw new Error("Invalid value for DomElems.currentTab in Bladeburner.createActionAndSkillsContent"); @@ -1577,351 +1594,18 @@ Bladeburner.prototype.createActionAndSkillsContent = function() { DomElems.actionAndSkillsDiv.appendChild(DomElems.actionsAndSkillsList); } -Bladeburner.prototype.createGeneralActionsContent = function() { - if (DomElems.actionsAndSkillsList == null || DomElems.actionsAndSkillsDesc == null) { - throw new Error("Bladeburner.createGeneralActionsContent called with either " + - "DomElems.actionsAndSkillsList or DomElems.actionsAndSkillsDesc = null"); - } - - DomElems.actionsAndSkillsDesc.innerText = - "These are generic actions that will assist you in your Bladeburner " + - "duties. They will not affect your Bladeburner rank in any way." - - for (var actionName in GeneralActions) { - if (GeneralActions.hasOwnProperty(actionName)) { - DomElems.generalActions[actionName] = createElement("div", { - class:"bladeburner-action", name:actionName, - }); - DomElems.actionsAndSkillsList.appendChild(DomElems.generalActions[actionName]); - } - } -} - -Bladeburner.prototype.createContractsContent = function() { - if (DomElems.actionsAndSkillsList == null || DomElems.actionsAndSkillsDesc == null) { - throw new Error("Bladeburner.createContractsContent called with either " + - "DomElems.actionsAndSkillsList or DomElems.actionsAndSkillsDesc = null"); - } - - DomElems.actionsAndSkillsDesc.innerHTML = - "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.

" + - "You can unlock higher-level contracts by successfully completing them. " + - "Higher-level contracts are more difficult, but grant more rank, experience, and money."; - - for (var contractName in this.contracts) { - if (this.contracts.hasOwnProperty(contractName)) { - DomElems.contracts[contractName] = createElement("div", { - class:"bladeburner-action", name:contractName, - }); - DomElems.actionsAndSkillsList.appendChild(DomElems.contracts[contractName]); - } - } -} - -Bladeburner.prototype.createOperationsContent = function() { - if (DomElems.actionsAndSkillsList == null || DomElems.actionsAndSkillsDesc == null) { - throw new Error("Bladeburner.createOperationsContent called with either " + - "DomElems.actionsAndSkillsList or DomElems.actionsAndSkillsDesc = null"); - } - - DomElems.actionsAndSkillsDesc.innerHTML = - "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.

" + - "Operations can affect the chaos level and Synthoid population of your " + - "current city. The exact effects vary between different Operations.

" + - "For operations, you can use a team. You must first recruit team members. " + - "Having a larger team will improves your chances of success.

" + - "You can unlock higher-level operations by successfully completing them. " + - "Higher-level operations are more difficult, but grant more rank and experience."; - - for (var operationName in this.operations) { - if (this.operations.hasOwnProperty(operationName)) { - DomElems.operations[operationName] = createElement("div", { - class:"bladeburner-action", name:operationName, - }); - DomElems.actionsAndSkillsList.appendChild(DomElems.operations[operationName]); - } - } -} - -Bladeburner.prototype.createBlackOpsContent = function() { - - 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.

" + - "Your ultimate goal to climb through the ranks of Bladeburners is to complete " + - "all of the Black Ops.

" + - "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]); - } -} - -Bladeburner.prototype.createSkillsContent = function() { - if (DomElems.actionsAndSkillsList == null || DomElems.actionsAndSkillsDesc == null) { - throw new Error("Bladeburner.createSkillsContent called with either " + - "DomElems.actionsAndSkillsList or DomElems.actionsAndSkillsDesc = null"); - } - - // Display Current multipliers - DomElems.actionsAndSkillsDesc.innerHTML = - "You will gain one skill point every " + BladeburnerConstants.RanksPerSkillPoint + " ranks.

" + - "Note that when upgrading a skill, the benefit for that skill is additive. " + - "However, the effects of different skills with each other is multiplicative.

" - 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]) { - case "successChanceAll": - DomElems.actionsAndSkillsDesc.innerHTML += "Total Success Chance: x" + mult + "
"; - break; - case "successChanceStealth": - DomElems.actionsAndSkillsDesc.innerHTML += "Stealth Success Chance: x" + mult + "
"; - break; - case "successChanceKill": - DomElems.actionsAndSkillsDesc.innerHTML += "Retirement Success Chance: x" + mult + "
"; - break; - case "successChanceContract": - DomElems.actionsAndSkillsDesc.innerHTML += "Contract Success Chance: x" + mult + "
"; - break; - case "successChanceOperation": - DomElems.actionsAndSkillsDesc.innerHTML += "Operation Success Chance: x" + mult + "
"; - break; - case "successChanceEstimate": - DomElems.actionsAndSkillsDesc.innerHTML += "Synthoid Data Estimate: x" + mult + "
"; - break; - case "actionTime": - DomElems.actionsAndSkillsDesc.innerHTML += "Action Time: x" + mult + "
"; - break; - case "effHack": - DomElems.actionsAndSkillsDesc.innerHTML += "Hacking Skill: x" + mult + "
"; - break; - case "effStr": - DomElems.actionsAndSkillsDesc.innerHTML += "Strength: x" + mult + "
"; - break; - case "effDef": - DomElems.actionsAndSkillsDesc.innerHTML += "Defense: x" + mult + "
"; - break; - case "effDex": - DomElems.actionsAndSkillsDesc.innerHTML += "Dexterity: x" + mult + "
"; - break; - case "effAgi": - DomElems.actionsAndSkillsDesc.innerHTML += "Agility: x" + mult + "
"; - break; - case "effCha": - DomElems.actionsAndSkillsDesc.innerHTML += "Charisma: x" + mult + "
"; - break; - case "effInt": - DomElems.actionsAndSkillsDesc.innerHTML += "Intelligence: x" + mult + "
"; - break; - case "stamina": - DomElems.actionsAndSkillsDesc.innerHTML += "Stamina: x" + mult + "
"; - break; - case "money": - DomElems.actionsAndSkillsDesc.innerHTML += "Contract Money: x" + mult + "
"; - break; - case "expGain": - DomElems.actionsAndSkillsDesc.innerHTML += "Exp Gain: x" + mult + "
"; - break; - default: - console.warn(`Unrecognized SkillMult Key: ${multKeys[i]}`); - break; - } - } - } - - // Skill Points - DomElems.skillPointsDisplay = createElement("p", { - innerHTML:"
Skill Points: " + formatNumber(this.skillPoints, 0) + "", - }); - DomElems.actionAndSkillsDiv.appendChild(DomElems.skillPointsDisplay); - - // UI Element for each skill - for (var skillName in Skills) { - if (Skills.hasOwnProperty(skillName)) { - DomElems.skills[skillName] = createElement("div", { - class:"bladeburner-action", name:skillName, - }); - DomElems.actionsAndSkillsList.appendChild(DomElems.skills[skillName]); - } - } -} - Bladeburner.prototype.updateContent = function() { this.updateOverviewContent(); - this.updateActionAndSkillsContent(); } Bladeburner.prototype.updateOverviewContent = function() { - if (!routing.isOn(Page.Bladeburner)) {return;} - DomElems.overviewRank.childNodes[0].nodeValue = "Rank: " + formatNumber(this.rank, 2); - DomElems.overviewStamina.innerText = "Stamina: " + formatNumber(this.stamina, 3) + " / " + formatNumber(this.maxStamina, 3); - ReactDOM.render(<> - Stamina Penalty: {formatNumber((1-this.calculateStaminaPenalty())*100, 1)}%

- Team Size: {formatNumber(this.teamSize, 0)}
- Team Members Lost: {formatNumber(this.teamLost, 0)}

- Num Times Hospitalized: {this.numHosp}
- Money Lost From Hospitalizations: {Money(this.moneyLost)}

- Current City: {this.city}
- , DomElems.overviewGen1); - - DomElems.overviewEstPop.childNodes[0].nodeValue = "Est. Synthoid Population: " + numeralWrapper.formatPopulation(this.getCurrentCity().popEst); - DomElems.overviewEstComms.childNodes[0].nodeValue = "Est. Synthoid Communities: " + formatNumber(this.getCurrentCity().comms, 0); - DomElems.overviewChaos.childNodes[0].nodeValue = "City Chaos: " + formatNumber(this.getCurrentCity().chaos); - DomElems.overviewSkillPoints.innerText = "Skill Points: " + formatNumber(this.skillPoints, 0); - DomElems.overviewBonusTime.childNodes[0].nodeValue = "Bonus time: " + convertTimeMsToTimeElapsedString(this.storedCycles/BladeburnerConstants.CyclesPerSecond*1000); - ReactDOM.render(StatsTable([ - ["Aug. Success Chance mult: ", formatNumber(Player.bladeburner_success_chance_mult*100, 1) + "%"], - ["Aug. Max Stamina mult: ", formatNumber(Player.bladeburner_max_stamina_mult*100, 1) + "%"], - ["Aug. Stamina Gain mult: ", formatNumber(Player.bladeburner_stamina_gain_mult*100, 1) + "%"], - ["Aug. Field Analysis mult: ", formatNumber(Player.bladeburner_analysis_mult*100, 1) + "%"], - ]), DomElems.overviewAugMults); + if (!routing.isOn(Page.Bladeburner)) return; + ReactDOM.render(, DomElems.overviewDiv); } -Bladeburner.prototype.updateActionAndSkillsContent = function() { - if (DomElems.currentTab == null) {DomElems.currentTab = "general";} - switch(DomElems.currentTab.toLowerCase()) { - case "general": - var actionElems = Object.keys(DomElems.generalActions); - for (var i = 0; i < actionElems.length; ++i) { - var actionElem = DomElems.generalActions[actionElems[i]]; - var name = actionElem.name; - var actionObj = GeneralActions[name]; - if (actionObj == null) { - throw new Error("Could not find Object " + name + " in Bladeburner.updateActionAndSkillsContent()"); - } - if (this.action.type === ActionTypes[name]) { - actionElem.classList.add(ActiveActionCssClass); - } else { - actionElem.classList.remove(ActiveActionCssClass); - } - this.updateGeneralActionsUIElement(actionElem, actionObj); - } - break; - case "contracts": - var contractElems = Object.keys(DomElems.contracts); - for (var i = 0; i < contractElems.length; ++i) { - var contractElem = DomElems.contracts[contractElems[i]]; - var name = contractElem.name; - if (this.action.type === ActionTypes["Contract"] && name === this.action.name) { - contractElem.classList.add(ActiveActionCssClass); - } else { - contractElem.classList.remove(ActiveActionCssClass); - } - var contract = this.contracts[name]; - if (contract == null) { - throw new Error("Could not find Contract " + name + " in Bladeburner.updateActionAndSkillsContent()"); - } - this.updateContractsUIElement(contractElem, contract); - } - break; - case "operations": - var operationElems = Object.keys(DomElems.operations); - for (var i = 0; i < operationElems.length; ++i) { - var operationElem = DomElems.operations[operationElems[i]]; - var name = operationElem.name; - if (this.action.type === ActionTypes["Operation"] && name === this.action.name) { - operationElem.classList.add(ActiveActionCssClass); - } else { - operationElem.classList.remove(ActiveActionCssClass); - } - var operation = this.operations[name]; - if (operation == null) { - throw new Error("Could not find Operation " + name + " in Bladeburner.updateActionAndSkillsContent()"); - } - this.updateOperationsUIElement(operationElem, operation); - } - break; - case "blackops": - var blackopsElems = Object.keys(DomElems.blackops); - for (var i = 0; i < blackopsElems.length; ++i) { - var blackopElem = DomElems.blackops[blackopsElems[i]]; - var name = blackopElem.name; - if (this.action.type === ActionTypes["BlackOperation"] && name === this.action.name) { - blackopElem.classList.add(ActiveActionCssClass); - } else { - blackopElem.classList.remove(ActiveActionCssClass); - } - var blackop = BlackOperations[name]; - if (blackop == null) { - throw new Error("Could not find BlackOperation " + name + " in Bladeburner.updateActionAndSkillsContent()"); - } - this.updateBlackOpsUIElement(blackopElem, blackop); - } - break; - case "skills": - DomElems.skillPointsDisplay.innerHTML = "
Skill Points: " + formatNumber(this.skillPoints, 0) + ""; - - var skillElems = Object.keys(DomElems.skills); - for (var i = 0; i < skillElems.length; ++i) { - var skillElem = DomElems.skills[skillElems[i]]; - var name = skillElem.name; - var skill = Skills[name]; - if (skill == null) { - throw new Error("Could not find Skill " + name + " in Bladeburner.updateActionAndSkillsContent()"); - } - this.updateSkillsUIElement(skillElem, skill); - } - break; - default: - throw new Error("Invalid value for DomElems.currentTab in Bladeburner.createActionAndSkillsContent"); - } -} - -Bladeburner.prototype.updateGeneralActionsUIElement = function(el, action) { - ReactDOM.unmountComponentAtNode(el); - ReactDOM.render(, el); -} - -Bladeburner.prototype.updateContractsUIElement = function(el, action) { - ReactDOM.unmountComponentAtNode(el); - ReactDOM.render(, el); -} - -Bladeburner.prototype.updateOperationsUIElement = function(el, action) { - ReactDOM.unmountComponentAtNode(el); - ReactDOM.render(, el); -} - -Bladeburner.prototype.updateBlackOpsUIElement = function(el, action) { - ReactDOM.unmountComponentAtNode(el); - ReactDOM.render(, el); -} - -Bladeburner.prototype.updateSkillsUIElement = function(el, skill) { - ReactDOM.unmountComponentAtNode(el); - ReactDOM.render(, el); -} +//////////////////////////////////////////////////////////////////////////////// +///////////////////////////////HYDRO END OF UI////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// // Bladeburner Console Window Bladeburner.prototype.postToConsole = function(input, saveToLogs=true) { @@ -2382,7 +2066,6 @@ Bladeburner.prototype.executeStartConsoleCommand = function(args) { this.action.type = ActionTypes[name]; this.action.name = name; this.startAction(this.action); - this.updateActionAndSkillsContent(); } else { this.postToConsole("Invalid action name specified: " + args[2]); } @@ -2393,7 +2076,6 @@ Bladeburner.prototype.executeStartConsoleCommand = function(args) { this.action.type = ActionTypes.Contract; this.action.name = name; this.startAction(this.action); - this.updateActionAndSkillsContent(); } else { this.postToConsole("Invalid contract name specified: " + args[2]); } @@ -2406,7 +2088,6 @@ Bladeburner.prototype.executeStartConsoleCommand = function(args) { this.action.type = ActionTypes.Operation; this.action.name = name; this.startAction(this.action); - this.updateActionAndSkillsContent(); } else { this.postToConsole("Invalid Operation name specified: " + args[2]); } @@ -2419,7 +2100,6 @@ Bladeburner.prototype.executeStartConsoleCommand = function(args) { this.action.type = ActionTypes.BlackOperation; this.action.name = name; this.startAction(this.action); - this.updateActionAndSkillsContent(); } else { this.postToConsole("Invalid BlackOp name specified: " + args[2]); } diff --git a/src/Bladeburner/ui/BlackOpList.tsx b/src/Bladeburner/ui/BlackOpList.tsx new file mode 100644 index 000000000..eeb0f9907 --- /dev/null +++ b/src/Bladeburner/ui/BlackOpList.tsx @@ -0,0 +1,42 @@ +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"; + +interface IProps { + bladeburner: any; +} + +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) => +

  • + +
  • + )} + ); +} \ No newline at end of file diff --git a/src/Bladeburner/ui/BlackOpPage.tsx b/src/Bladeburner/ui/BlackOpPage.tsx new file mode 100644 index 000000000..3682a5398 --- /dev/null +++ b/src/Bladeburner/ui/BlackOpPage.tsx @@ -0,0 +1,25 @@ +import * as React from "react"; +import { BlackOpList } from "./BlackOpList"; + +interface IProps { + bladeburner: any; +} + +export function BlackOpPage(props: IProps): React.ReactElement { + return (<> +

    + Black Operations (Black Ops) are special, one-time covert operations. + Each Black Op must be unlocked successively by completing + the one before it. +
    +
    + Your ultimate goal to climb through the ranks of Bladeburners is to complete + all of the Black Ops. +
    +
    + Like normal operations, you may use a team for Black Ops. Failing + a black op will incur heavy HP and rank losses. +

    + + ); +} \ No newline at end of file diff --git a/src/Bladeburner/ui/ContractList.tsx b/src/Bladeburner/ui/ContractList.tsx new file mode 100644 index 000000000..142eb5ce0 --- /dev/null +++ b/src/Bladeburner/ui/ContractList.tsx @@ -0,0 +1,30 @@ +import React, { useState, useEffect } from "react"; +import { + formatNumber, + convertTimeMsToTimeElapsedString, +} from "../../../utils/StringHelperFunctions"; +import { ContractElem } from "./ContractElem"; +import { Contract } from "../Contract"; + +interface IProps { + bladeburner: any; +} + +export function ContractList(props: IProps): React.ReactElement { + const setRerender = useState(false)[1]; + + useEffect(() => { + const id = setInterval(() => setRerender(old => !old), 1000); + return () => clearInterval(id); + }, []); + + const names = Object.keys(props.bladeburner.contracts); + const contracts = props.bladeburner.contracts; + return (<> + {names.map((name: string) => +
  • + +
  • + )} + ); +} \ No newline at end of file diff --git a/src/Bladeburner/ui/ContractPage.tsx b/src/Bladeburner/ui/ContractPage.tsx new file mode 100644 index 000000000..b64a0b4ae --- /dev/null +++ b/src/Bladeburner/ui/ContractPage.tsx @@ -0,0 +1,20 @@ +import * as React from "react"; +import { ContractList } from "./ContractList"; + +interface IProps { + bladeburner: any; +} + +export function ContractPage(props: IProps): React.ReactElement { + return (<> +

    + 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. +
    +
    + You can unlock higher-level contracts by successfully completing them. + Higher-level contracts are more difficult, but grant more rank, experience, and money. +

    + + ); +} \ No newline at end of file diff --git a/src/Bladeburner/ui/GeneralActionList.tsx b/src/Bladeburner/ui/GeneralActionList.tsx new file mode 100644 index 000000000..15f329df9 --- /dev/null +++ b/src/Bladeburner/ui/GeneralActionList.tsx @@ -0,0 +1,35 @@ +import React, { useState, useEffect } from "react"; +import { + formatNumber, + convertTimeMsToTimeElapsedString, +} from "../../../utils/StringHelperFunctions"; +import { GeneralActionElem } from "./GeneralActionElem"; +import { Action } from "../Action"; +import { GeneralActions } from "../GeneralActions"; + +interface IProps { + bladeburner: any; +} + +export function GeneralActionList(props: IProps): React.ReactElement { + const setRerender = useState(false)[1]; + + useEffect(() => { + const id = setInterval(() => setRerender(old => !old), 1000); + return () => clearInterval(id); + }, []); + + const actions: Action[] = []; + for (const name in GeneralActions) { + if (GeneralActions.hasOwnProperty(name)) { + actions.push(GeneralActions[name]); + } + } + return (<> + {actions.map((action: Action) => +
  • + +
  • + )} + ); +} \ No newline at end of file diff --git a/src/Bladeburner/ui/GeneralActionPage.tsx b/src/Bladeburner/ui/GeneralActionPage.tsx new file mode 100644 index 000000000..ea1b601f3 --- /dev/null +++ b/src/Bladeburner/ui/GeneralActionPage.tsx @@ -0,0 +1,16 @@ +import * as React from "react"; +import { GeneralActionList } from "./GeneralActionList"; + +interface IProps { + bladeburner: any; +} + +export function GeneralActionPage(props: IProps): React.ReactElement { + return (<> +

    + These are generic actions that will assist you in your Bladeburner + duties. They will not affect your Bladeburner rank in any way. +

    + + ); +} \ No newline at end of file diff --git a/src/Bladeburner/ui/OperationList.tsx b/src/Bladeburner/ui/OperationList.tsx new file mode 100644 index 000000000..bff9cc3b7 --- /dev/null +++ b/src/Bladeburner/ui/OperationList.tsx @@ -0,0 +1,30 @@ +import React, { useState, useEffect } from "react"; +import { + formatNumber, + convertTimeMsToTimeElapsedString, +} from "../../../utils/StringHelperFunctions"; +import { OperationElem } from "./OperationElem"; +import { Operation } from "../Operation"; + +interface IProps { + bladeburner: any; +} + +export function OperationList(props: IProps): React.ReactElement { + const setRerender = useState(false)[1]; + + useEffect(() => { + const id = setInterval(() => setRerender(old => !old), 1000); + return () => clearInterval(id); + }, []); + + const names = Object.keys(props.bladeburner.operations); + const operations = props.bladeburner.operations; + return (<> + {names.map((name: string) => +
  • + +
  • + )} + ); +} \ No newline at end of file diff --git a/src/Bladeburner/ui/OperationPage.tsx b/src/Bladeburner/ui/OperationPage.tsx new file mode 100644 index 000000000..3da625131 --- /dev/null +++ b/src/Bladeburner/ui/OperationPage.tsx @@ -0,0 +1,31 @@ +import * as React from "react"; +import { OperationList } from "./OperationList"; + +interface IProps { + bladeburner: any; +} + +export function OperationPage(props: IProps): React.ReactElement { + return (<> +

    + 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. +
    +
    + Operations can affect the chaos level and Synthoid population of your + current city. The exact effects vary between different Operations. +
    +
    + For operations, you can use a team. You must first recruit team members. + Having a larger team will improves your chances of success. +
    +
    + You can unlock higher-level operations by successfully completing them. + Higher-level operations are more difficult, but grant more rank and experience. +

    + + ); +} \ No newline at end of file diff --git a/src/Bladeburner/ui/SkillList.tsx b/src/Bladeburner/ui/SkillList.tsx new file mode 100644 index 000000000..014b6f992 --- /dev/null +++ b/src/Bladeburner/ui/SkillList.tsx @@ -0,0 +1,17 @@ +import * as React from "react"; +import { SkillElem } from "./SkillElem"; +import { Skills } from "../Skills"; + +interface IProps { + bladeburner: any; +} + +export function SkillList(props: IProps): React.ReactElement { + return (<> + {Object.keys(Skills).map((skill: string) => +
  • + +
  • + )} + ); +} \ No newline at end of file diff --git a/src/Bladeburner/ui/SkillPage.tsx b/src/Bladeburner/ui/SkillPage.tsx new file mode 100644 index 000000000..8a6e7e9bc --- /dev/null +++ b/src/Bladeburner/ui/SkillPage.tsx @@ -0,0 +1,66 @@ +import * as React from "react"; +import { SkillList } from "./SkillList"; +import { BladeburnerConstants } from "../data/Constants"; +import { formatNumber } from "../../../utils/StringHelperFunctions"; + +interface IProps { + bladeburner: any; +} + + +export function SkillPage(props: IProps): React.ReactElement { + const mults = props.bladeburner.skillMultipliers; + + function valid(mult: any) { + return mult && mult !== 1 + } + + return (<> + Skill Points: {formatNumber(props.bladeburner.skillPoints, 0)} +

    + You will gain one skill point every {BladeburnerConstants.RanksPerSkillPoint} ranks. +
    +
    + Note that when upgrading a skill, the benefit for that skill is additive. + However, the effects of different skills with each other is multiplicative. +
    +
    +

    + {valid(mults["successChanceAll"]) && <>Total Success Chance: x{formatNumber(mults["successChanceAll"], 3)}
    } + {valid(mults["successChanceStealth"]) && <>Stealth Success Chance: x{formatNumber(mults["successChanceStealth"], 3)}
    } + {valid(mults["successChanceKill"]) && <>Retirement Success Chance: x{formatNumber(mults["successChanceKill"], 3)}
    } + {valid(mults["successChanceContract"]) && <>Contract Success Chance: x{formatNumber(mults["successChanceContract"], 3)}
    } + {valid(mults["successChanceOperation"]) && <>Operation Success Chance: x{formatNumber(mults["successChanceOperation"], 3)}
    } + {valid(mults["successChanceEstimate"]) && <>Synthoid Data Estimate: x{formatNumber(mults["successChanceEstimate"], 3)}
    } + {valid(mults["actionTime"]) && <>Action Time: x{formatNumber(mults["actionTime"], 3)}
    } + {valid(mults["effHack"]) && <>Hacking Skill: x{formatNumber(mults["effHack"], 3)}
    } + {valid(mults["effStr"]) && <>Strength: x{formatNumber(mults["effStr"], 3)}
    } + {valid(mults["effDef"]) && <>Defense: x{formatNumber(mults["effDef"], 3)}
    } + {valid(mults["effDex"]) && <>Dexterity: x{formatNumber(mults["effDex"], 3)}
    } + {valid(mults["effAgi"]) && <>Agility: x{formatNumber(mults["effAgi"], 3)}
    } + {valid(mults["effCha"]) && <>Charisma: x{formatNumber(mults["effCha"], 3)}
    } + {valid(mults["effInt"]) && <>Intelligence: x{formatNumber(mults["effInt"], 3)}
    } + {valid(mults["stamina"]) && <>Stamina: x{formatNumber(mults["stamina"], 3)}
    } + {valid(mults["money"]) && <>Contract Money: x{formatNumber(mults["money"], 3)}
    } + {valid(mults["expGain"]) && <>Exp Gain: x{formatNumber(mults["expGain"], 3)}
    } +
    + + ); +} + +/* + + + + +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]) { + + } + } +} +*/ \ No newline at end of file diff --git a/src/Bladeburner/ui/Stats.tsx b/src/Bladeburner/ui/Stats.tsx new file mode 100644 index 000000000..9e072f189 --- /dev/null +++ b/src/Bladeburner/ui/Stats.tsx @@ -0,0 +1,78 @@ +import React, { useState, useEffect } from "react"; +import { + formatNumber, + convertTimeMsToTimeElapsedString, +} from "../../../utils/StringHelperFunctions"; +import { BladeburnerConstants } from "../data/Constants"; +import { IPlayer } from "../../PersonObjects/IPlayer"; +import { Money } from "../../ui/React/Money"; +import { StatsTable } from "../../ui/React/StatsTable"; +import { numeralWrapper } from "../../ui/numeralFormat"; +import { dialogBoxCreate } from "../../../utils/DialogBox"; + +interface IProps { + bladeburner: any; + 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.

    " + + "Your max stamina is determined primarily by your agility stat.

    " + + "Your stamina gain rate is determined by both your agility and your " + + "max stamina. Higher max stamina leads to a higher gain rate.

    " + + "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).

    " + + "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.

    " + + "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.

    " + + "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."); + } + + return (

    + Rank: {formatNumber(props.bladeburner.rank, 2)}
    + Stamina: {formatNumber(props.bladeburner.stamina, 3)} / {formatNumber(props.bladeburner.maxStamina, 3)} +

    ?

    + Est. Synthoid Population: {numeralWrapper.formatPopulation(props.bladeburner.getCurrentCity().popEst)} +
    ?

    + Est. Synthoid Communities: {formatNumber(props.bladeburner.getCurrentCity().comms, 0)}
    + City Chaos: {formatNumber(props.bladeburner.getCurrentCity().chaos)}
    + Skill Points: {formatNumber(props.bladeburner.skillPoints, 0)}
    + Bonus time: {convertTimeMsToTimeElapsedString(props.bladeburner.storedCycles/BladeburnerConstants.CyclesPerSecond*1000)}
    + Stamina Penalty: {formatNumber((1-props.bladeburner.calculateStaminaPenalty())*100, 1)}%

    + Team Size: {formatNumber(props.bladeburner.teamSize, 0)}
    + Team Members Lost: {formatNumber(props.bladeburner.teamLost, 0)}

    + Num Times Hospitalized: {props.bladeburner.numHosp}
    + Money Lost From Hospitalizations: {Money(props.bladeburner.moneyLost)}

    + Current City: {props.bladeburner.city}
    + {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) + "%"], + ])} +

    ); +} \ No newline at end of file diff --git a/src/ui/CharacterInfo.tsx b/src/ui/CharacterInfo.tsx index 91f335f8f..f89a63c0e 100644 --- a/src/ui/CharacterInfo.tsx +++ b/src/ui/CharacterInfo.tsx @@ -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 { {`Servers owned: ${p.purchasedServers.length} / ${getPurchaseServerLimit()}`}
    {`Augmentations installed: ${p.augmentations.length}`}

    - {StatsTable(timeRows, null)} + {StatsTable(timeRows)}
    diff --git a/src/ui/React/StatsTable.tsx b/src/ui/React/StatsTable.tsx index 9518a9fec..b2db010e6 100644 --- a/src/ui/React/StatsTable.tsx +++ b/src/ui/React/StatsTable.tsx @@ -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 = <>

    {title}


    ; diff --git a/utils/StringHelperFunctions.ts b/utils/StringHelperFunctions.ts index bf8b395b9..2cde62156 100644 --- a/utils/StringHelperFunctions.ts +++ b/utils/StringHelperFunctions.ts @@ -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: number = 0): string { return num.toLocaleString(undefined, { maximumFractionDigits: numFractionDigits, minimumFractionDigits: numFractionDigits, From 33f0efd49c6c355c0e6a0c05617b862088fdd1ae Mon Sep 17 00:00:00 2001 From: Olivier Gagnon Date: Tue, 10 Aug 2021 11:25:21 -0400 Subject: [PATCH 03/15] converting more blade to react --- src/Bladeburner.jsx | 355 ++--------------------- src/Bladeburner/ui/AllPages.tsx | 46 +++ src/Bladeburner/ui/BlackOpElem.tsx | 5 +- src/Bladeburner/ui/Console.tsx | 43 +++ src/Bladeburner/ui/ContractElem.tsx | 1 - src/Bladeburner/ui/ContractList.tsx | 7 - src/Bladeburner/ui/GeneralActionList.tsx | 7 - src/Bladeburner/ui/OperationElem.tsx | 1 - src/Bladeburner/ui/OperationList.tsx | 7 - src/Bladeburner/ui/SkillElem.tsx | 5 +- src/Bladeburner/ui/SkillList.tsx | 3 +- src/Bladeburner/ui/SkillPage.tsx | 45 +-- src/Bladeburner/ui/Stats.tsx | 109 ++++++- 13 files changed, 234 insertions(+), 400 deletions(-) create mode 100644 src/Bladeburner/ui/AllPages.tsx create mode 100644 src/Bladeburner/ui/Console.tsx diff --git a/src/Bladeburner.jsx b/src/Bladeburner.jsx index 41f4ddafc..4693f99b2 100644 --- a/src/Bladeburner.jsx +++ b/src/Bladeburner.jsx @@ -68,6 +68,8 @@ import { OperationPage } from "./Bladeburner/ui/OperationPage"; import { BlackOpPage } from "./Bladeburner/ui/BlackOpPage"; import { SkillPage } from "./Bladeburner/ui/SkillPage"; import { Stats } from "./Bladeburner/ui/Stats"; +import { AllPages } from "./Bladeburner/ui/AllPages"; +import { Console } from "./Bladeburner/ui/Console"; import { StatsTable } from "./ui/React/StatsTable"; import { CopyableText } from "./ui/React/CopyableText"; @@ -443,9 +445,6 @@ Bladeburner.prototype.process = function() { } } - if (routing.isOn(Page.Bladeburner)) { - this.updateContent(); - } } } @@ -809,10 +808,6 @@ Bladeburner.prototype.completeAction = function() { return hackWorldDaemon(Player.bitNodeN); } - if (routing.isOn(Page.Bladeburner)) { - this.createActionAndSkillsContent(); - } - if (this.logging.blackops) { this.log(action.name + " successful! Gained " + formatNumber(rankGain, 1) + " rank"); } @@ -1228,7 +1223,6 @@ Bladeburner.prototype.initializeDomElementRefs = function() { overviewDiv: null, // Overview of stats that stays fixed on left actionAndSkillsDiv: null, // Panel for different sections (contracts, ops, skills) - currentTab: null, // Contracts, Operations, Black Ops, Skills consoleDiv: null, consoleTable: null, @@ -1236,29 +1230,6 @@ Bladeburner.prototype.initializeDomElementRefs = function() { consoleInputCell: null, // td consoleInputHeader: null, // "> " consoleInput: null, // Actual input element - - // Overview Content - overviewRank: null, - overviewStamina: null, - overviewStaminaHelpTip: null, - overviewGen1: null, // Stamina Penalty, Team, Hospitalized stats, current city - overviewEstPop: null, - overviewEstPopHelpTip: null, - overviewEstComms: null, - overviewChaos: null, - overviewSkillPoints: null, - overviewBonusTime: null, - overviewAugMults: null, - - // Actions and Skills Content - actionsAndSkillsDesc: null, - actionsAndSkillsList: null, // ul element of all UI elements in this panel - generalActions: {}, - contracts: {}, - operations: {}, - blackops: {}, - skills: {}, - skillPointsDisplay: null, }; } @@ -1282,10 +1253,8 @@ Bladeburner.prototype.createContent = function() { border:"1px solid white", margin:"6px", padding:"6px", }); - DomElems.currentTab = "general"; - - this.createOverviewContent(); - this.createActionAndSkillsContent(); + ReactDOM.render(, DomElems.overviewDiv); + ReactDOM.render(, DomElems.actionAndSkillsDiv); // Console DomElems.consoleDiv = createElement("div", { @@ -1297,32 +1266,13 @@ Bladeburner.prototype.createContent = function() { return false; }, }); - DomElems.consoleTable = createElement("table", {class:"bladeburner-console-table"}); - DomElems.consoleInputRow = createElement("tr", {class:"bladeburner-console-input-row", id:"bladeburner-console-input-row"}); - DomElems.consoleInputCell = createElement("td", {class:"bladeburner-console-input-cell"}); - DomElems.consoleInputHeader = createElement("pre", {innerText:"> "}); - DomElems.consoleInput = createElement("input", { - type:"text", class:"bladeburner-console-input", tabIndex:1, - onfocus:() => {DomElems.consoleInput.value = DomElems.consoleInput.value}, - }); - - DomElems.consoleInputCell.appendChild(DomElems.consoleInputHeader); - DomElems.consoleInputCell.appendChild(DomElems.consoleInput); - DomElems.consoleInputRow.appendChild(DomElems.consoleInputCell); - DomElems.consoleTable.appendChild(DomElems.consoleInputRow); - DomElems.consoleDiv.appendChild(DomElems.consoleTable); + ReactDOM.render(, DomElems.consoleDiv); DomElems.overviewConsoleParentDiv.appendChild(DomElems.overviewDiv); DomElems.overviewConsoleParentDiv.appendChild(DomElems.consoleDiv); DomElems.bladeburnerDiv.appendChild(DomElems.overviewConsoleParentDiv); DomElems.bladeburnerDiv.appendChild(DomElems.actionAndSkillsDiv); - - // legend - const legend = createElement("div") - legend.innerHTML = `${stealthIcon}= This action requires stealth, ${killIcon} = This action involves retirement` - DomElems.bladeburnerDiv.appendChild(legend); - document.getElementById("entire-game-container").appendChild(DomElems.bladeburnerDiv); if (this.consoleLogs.length === 0) { @@ -1333,8 +1283,6 @@ Bladeburner.prototype.createContent = function() { this.postToConsole(this.consoleLogs[i], false); } } - - DomElems.consoleInput.focus(); } Bladeburner.prototype.clearContent = function() { @@ -1346,290 +1294,35 @@ Bladeburner.prototype.clearContent = function() { this.initializeDomElementRefs(); } -Bladeburner.prototype.createOverviewContent = function() { - if (DomElems.overviewDiv == null) { - throw new Error("Bladeburner.createOverviewContent() called with DomElems.overviewDiv = null"); - } - - DomElems.overviewRank = createElement("p", { - innerText:"Rank: ", - display:"inline-block", - tooltip:"Your rank within the Bladeburner division", - }); - - DomElems.overviewStamina = createElement("p", { - display:"inline-block", - }); - - DomElems.overviewStaminaHelpTip = createElement("div", { - class:"help-tip", - innerText:"?", - clickListener: () => { - dialogBoxCreate("Performing actions will use up your stamina.

    " + - "Your max stamina is determined primarily by your agility stat.

    " + - "Your stamina gain rate is determined by both your agility and your " + - "max stamina. Higher max stamina leads to a higher gain rate.

    " + - "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).

    " + - "Your max stamina and stamina gain rate can also be increased by " + - "training, or through skills and Augmentation upgrades."); - }, - }); - - DomElems.overviewGen1 = createElement("p", { - display:"block", - }); - - DomElems.overviewEstPop = createElement("p", { - innerText:"Est. Synthoid Population: ", - display:"inline-block", - tooltip:"This is your Bladeburner division's estimate of how many Synthoids exist " + - "in your current city.", - }); - - DomElems.overviewEstPopHelpTip = createElement("div", { - innerText:"?", class:"help-tip", - clickListener:() => { - 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.

    " + - "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.

    " + - "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."); - }, - }); - - DomElems.overviewEstComms = createElement("p", { - innerText:"Est. Synthoid Communities: ", - display:"inline-block", - tooltip:"This is your Bladeburner divison's estimate of how many Synthoid " + - "communities exist in your current city.", - }); - - DomElems.overviewChaos = createElement("p", { - innerText:"City Chaos: ", - display:"inline-block", - tooltip:"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.", - }); - - DomElems.overviewBonusTime = createElement("p", { - innerText: "Bonus time: ", - display: "inline-block", - tooltip: "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.", - }); - DomElems.overviewSkillPoints = createElement("p", {display:"block"}); - - - DomElems.overviewAugMults = createElement("div", {display:"block"}); - - - DomElems.overviewDiv.appendChild(DomElems.overviewRank); - appendLineBreaks(DomElems.overviewDiv, 1); - DomElems.overviewDiv.appendChild(DomElems.overviewStamina); - DomElems.overviewDiv.appendChild(DomElems.overviewStaminaHelpTip); - DomElems.overviewDiv.appendChild(DomElems.overviewGen1); - DomElems.overviewDiv.appendChild(DomElems.overviewEstPop); - DomElems.overviewDiv.appendChild(DomElems.overviewEstPopHelpTip); - appendLineBreaks(DomElems.overviewDiv, 1); - DomElems.overviewDiv.appendChild(DomElems.overviewEstComms); - appendLineBreaks(DomElems.overviewDiv, 1); - DomElems.overviewDiv.appendChild(DomElems.overviewChaos); - appendLineBreaks(DomElems.overviewDiv, 2); - DomElems.overviewDiv.appendChild(DomElems.overviewBonusTime); - DomElems.overviewDiv.appendChild(DomElems.overviewSkillPoints); - appendLineBreaks(DomElems.overviewDiv, 1); - DomElems.overviewDiv.appendChild(DomElems.overviewAugMults); - - // Travel to new city button - appendLineBreaks(DomElems.overviewDiv, 1); - DomElems.overviewDiv.appendChild(createElement("a", { - innerHTML:"Travel", class:"a-link-button", display:"inline-block", - clickListener:() => { - var popupId = "bladeburner-travel-popup-cancel-btn"; - var popupArguments = []; - popupArguments.push(createElement("a", { // Cancel Button - innerText:"Cancel", class:"a-link-button", - clickListener:() => { - removeElementById(popupId); return false; - }, - })) - popupArguments.push(createElement("p", { // Info Text - innerText:"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", - })); - for (var i = 0; i < BladeburnerConstants.CityNames.length; ++i) { - (function(inst, i) { - popupArguments.push(createElement("div", { - /** - * Reusing this css class...it adds a border and makes it - * so that background color changes when you hover - */ - class:"cmpy-mgmt-find-employee-option", - innerText:BladeburnerConstants.CityNames[i], - clickListener:() => { - inst.city = BladeburnerConstants.CityNames[i]; - removeElementById(popupId); - inst.updateOverviewContent(); - return false; - }, - })); - })(this, i); - } - createPopup(popupId, popupArguments); - }, - })); - - // Faction button - const bladeburnersFactionName = "Bladeburners"; - if (factionExists(bladeburnersFactionName)) { - var bladeburnerFac = Factions[bladeburnersFactionName]; - if (!(bladeburnerFac instanceof Faction)) { - throw new Error("Could not properly get Bladeburner Faction object in Bladeburner UI Overview Faction button"); - } - DomElems.overviewDiv.appendChild(createElement("a", { - innerText:"Faction", class:"a-link-button", display:"inline-block", - tooltip:"Apply to the Bladeburner Faction, or go to the faction page if you are already a member", - clickListener:() => { - if (bladeburnerFac.isMember) { - Engine.loadFactionContent(); - displayFactionContent(bladeburnersFactionName); - } else { - if (this.rank >= BladeburnerConstants.RankNeededForFaction) { - joinFaction(bladeburnerFac); - dialogBoxCreate("Congratulations! You were accepted into the Bladeburners faction"); - removeChildrenFromElement(DomElems.overviewDiv); - this.createOverviewContent(); - } else { - dialogBoxCreate("You need a rank of 25 to join the Bladeburners Faction!") - } - } - return false; - }, - })); - } - - DomElems.overviewDiv.appendChild(createElement("br")); - DomElems.overviewDiv.appendChild(createElement("br")); - - this.updateOverviewContent(); -} - -Bladeburner.prototype.createActionAndSkillsContent = function() { - if (DomElems.currentTab == null) {DomElems.currentTab = "general";} - - removeChildrenFromElement(DomElems.actionAndSkillsDiv); - clearObject(DomElems.generalActions); - clearObject(DomElems.contracts); - clearObject(DomElems.operations); - clearObject(DomElems.blackops); - clearObject(DomElems.skills); - - //Navigation buttons - var currTab = DomElems.currentTab.toLowerCase(); - var buttons = ["General", "Contracts", "Operations", "BlackOps", "Skills"]; - for (var i = 0; i < buttons.length; ++i) { - (function(buttons, i, inst, currTab) { - - DomElems.actionAndSkillsDiv.appendChild(createElement("a", { - innerText:buttons[i], - class:currTab === buttons[i].toLowerCase() ? "bladeburner-nav-button-inactive" : "bladeburner-nav-button", - clickListener:() => { - DomElems.currentTab = buttons[i].toLowerCase(); - inst.createActionAndSkillsContent(); - return false; - }, - })); - }) (buttons, i, this, currTab); - } - - // General info/description for each action - DomElems.actionsAndSkillsDesc = createElement("p", { - display:"block", margin:"4px", padding:"4px", - }); - - // List for actions/skills - removeChildrenFromElement(DomElems.actionsAndSkillsList); - DomElems.actionsAndSkillsList = createElement("ul"); - - switch(currTab) { - case "general": - ReactDOM.unmountComponentAtNode(DomElems.actionsAndSkillsDesc); - ReactDOM.render(, DomElems.actionsAndSkillsDesc); - break; - case "contracts": - ReactDOM.unmountComponentAtNode(DomElems.actionsAndSkillsDesc); - ReactDOM.render(, DomElems.actionsAndSkillsDesc); - break; - case "operations": - ReactDOM.unmountComponentAtNode(DomElems.actionsAndSkillsDesc); - ReactDOM.render(, DomElems.actionsAndSkillsDesc); - break; - case "blackops": - ReactDOM.unmountComponentAtNode(DomElems.actionsAndSkillsDesc); - ReactDOM.render(, DomElems.actionsAndSkillsDesc); - break; - case "skills": - ReactDOM.unmountComponentAtNode(DomElems.actionsAndSkillsDesc); - ReactDOM.render(, DomElems.actionsAndSkillsDesc); - break; - default: - throw new Error("Invalid value for DomElems.currentTab in Bladeburner.createActionAndSkillsContent"); - } - this.updateContent(); - - DomElems.actionAndSkillsDiv.appendChild(DomElems.actionsAndSkillsDesc); - DomElems.actionAndSkillsDiv.appendChild(DomElems.actionsAndSkillsList); -} - -Bladeburner.prototype.updateContent = function() { - this.updateOverviewContent(); -} - -Bladeburner.prototype.updateOverviewContent = function() { - if (!routing.isOn(Page.Bladeburner)) return; - ReactDOM.render(, DomElems.overviewDiv); -} - +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// ///////////////////////////////HYDRO END OF UI////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// // Bladeburner Console Window Bladeburner.prototype.postToConsole = function(input, saveToLogs=true) { const MaxConsoleEntries = 100; - if (saveToLogs === true) { + if (saveToLogs) { this.consoleLogs.push(input); if (this.consoleLogs.length > MaxConsoleEntries) { this.consoleLogs.shift(); } } - - if (input == null || DomElems.consoleDiv == null) {return;} - $("#bladeburner-console-input-row").before('' + input + ''); - - if (DomElems.consoleTable.childNodes.length > MaxConsoleEntries) { - DomElems.consoleTable.removeChild(DomElems.consoleTable.firstChild); - } - - this.updateConsoleScroll(); } -Bladeburner.prototype.updateConsoleScroll = function() { - DomElems.consoleDiv.scrollTop = DomElems.consoleDiv.scrollHeight; -} Bladeburner.prototype.resetConsoleInput = function() { DomElems.consoleInput.value = ""; @@ -2035,7 +1728,6 @@ Bladeburner.prototype.executeSkillConsoleCommand = function(args) { this.skillPoints -= pointCost; this.upgradeSkill(skill); this.log(skill.name + " upgraded to Level " + this.skills[skillName]); - this.createActionAndSkillsContent(); } else { this.postToConsole("You do not have enough Skill Points to upgrade this. You need " + formatNumber(pointCost, 0)); } @@ -2441,9 +2133,6 @@ Bladeburner.prototype.upgradeSkillNetscriptFn = function(skillName, workerScript this.skillPoints -= cost; this.upgradeSkill(skill); - if (routing.isOn(Page.Bladeburner) && DomElems.currentTab.toLowerCase() === "skills") { - this.createActionAndSkillsContent(); - } workerScript.log("bladeburner.upgradeSkill", `'${skillName}' upgraded to level ${this.skills[skillName]}`); return true; } @@ -2514,10 +2203,6 @@ Bladeburner.prototype.joinBladeburnerFactionNetscriptFn = function(workerScript) } else if (this.rank >= BladeburnerConstants.RankNeededForFaction) { joinFaction(bladeburnerFac); workerScript.log("bladeburner.joinBladeburnerFaction", "Joined Bladeburners faction."); - if (routing.isOn(Page.Bladeburner)) { - removeChildrenFromElement(DomElems.overviewDiv); - this.createOverviewContent(); - } return true; } else { workerScript.log("bladeburner.joinBladeburnerFaction", `You do not have the required rank (${this.rank}/${BladeburnerConstants.RankNeededForFaction}).`); diff --git a/src/Bladeburner/ui/AllPages.tsx b/src/Bladeburner/ui/AllPages.tsx new file mode 100644 index 000000000..deadaf103 --- /dev/null +++ b/src/Bladeburner/ui/AllPages.tsx @@ -0,0 +1,46 @@ +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"; + +interface IProps { + bladeburner: any; +} + +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 (setPage(props.name)} + className={page !== props.name ? + "bladeburner-nav-button" : + "bladeburner-nav-button-inactive"}> + {props.name} + ); + } + return (<> +
    +
    +
    +
    +
    +
    + {page === 'General' && } + {page === 'Contracts' && } + {page === 'Operations' && } + {page === 'BlackOps' && } + {page === 'Skills' && } +
    + {stealthIcon}= This action requires stealth, {killIcon} = This action involves retirement + ); +} \ No newline at end of file diff --git a/src/Bladeburner/ui/BlackOpElem.tsx b/src/Bladeburner/ui/BlackOpElem.tsx index bcdd65855..3fd3268f9 100644 --- a/src/Bladeburner/ui/BlackOpElem.tsx +++ b/src/Bladeburner/ui/BlackOpElem.tsx @@ -1,4 +1,4 @@ -import * as React from "react"; +import React, { useState } from "react"; import { formatNumber, convertTimeMsToTimeElapsedString, @@ -13,6 +13,7 @@ interface IProps { } export function BlackOpElem(props: IProps): React.ReactElement { + const setRerender = useState(false)[1]; const isCompleted = (props.bladeburner.blackops[props.action.name] != null); if(isCompleted) { return ( @@ -29,7 +30,7 @@ export function BlackOpElem(props: IProps): React.ReactElement { props.bladeburner.action.type = ActionTypes.BlackOperation; props.bladeburner.action.name = props.action.name; props.bladeburner.startAction(props.bladeburner.action); - props.bladeburner.updateActionAndSkillsContent(); + setRerender(old => !old); } function onTeam() { diff --git a/src/Bladeburner/ui/Console.tsx b/src/Bladeburner/ui/Console.tsx new file mode 100644 index 000000000..7c4de7eb8 --- /dev/null +++ b/src/Bladeburner/ui/Console.tsx @@ -0,0 +1,43 @@ +import React, { useState, useRef, useEffect } from "react"; + +interface ILineProps { + content: any; +} + +function Line(props: ILineProps): React.ReactElement { + return ( + {props.content} + ) +} + +interface IProps { + bladeburner: any; +} + +export function Console(props: IProps): React.ReactElement { + const lastRef = useRef(null); + const setRerender = useState(false)[1]; + + useEffect(() => { + if(lastRef.current) + lastRef.current.scrollIntoView({block: "end", inline: "nearest", behavior: "smooth" }); + const id = setInterval(() => setRerender(old => !old), 1000); + return () => clearInterval(id); + }, []); + + return ( + + {/* + 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) => )} + + + + +
    +
    {"> "}
    +
    ); +} \ No newline at end of file diff --git a/src/Bladeburner/ui/ContractElem.tsx b/src/Bladeburner/ui/ContractElem.tsx index 9ab48bba1..5ffc7d1e9 100644 --- a/src/Bladeburner/ui/ContractElem.tsx +++ b/src/Bladeburner/ui/ContractElem.tsx @@ -26,7 +26,6 @@ export function ContractElem(props: IProps): React.ReactElement { 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); } diff --git a/src/Bladeburner/ui/ContractList.tsx b/src/Bladeburner/ui/ContractList.tsx index 142eb5ce0..dcea7ea5e 100644 --- a/src/Bladeburner/ui/ContractList.tsx +++ b/src/Bladeburner/ui/ContractList.tsx @@ -11,13 +11,6 @@ interface IProps { } export function ContractList(props: IProps): React.ReactElement { - const setRerender = useState(false)[1]; - - useEffect(() => { - const id = setInterval(() => setRerender(old => !old), 1000); - return () => clearInterval(id); - }, []); - const names = Object.keys(props.bladeburner.contracts); const contracts = props.bladeburner.contracts; return (<> diff --git a/src/Bladeburner/ui/GeneralActionList.tsx b/src/Bladeburner/ui/GeneralActionList.tsx index 15f329df9..e459899d4 100644 --- a/src/Bladeburner/ui/GeneralActionList.tsx +++ b/src/Bladeburner/ui/GeneralActionList.tsx @@ -12,13 +12,6 @@ interface IProps { } export function GeneralActionList(props: IProps): React.ReactElement { - const setRerender = useState(false)[1]; - - useEffect(() => { - const id = setInterval(() => setRerender(old => !old), 1000); - return () => clearInterval(id); - }, []); - const actions: Action[] = []; for (const name in GeneralActions) { if (GeneralActions.hasOwnProperty(name)) { diff --git a/src/Bladeburner/ui/OperationElem.tsx b/src/Bladeburner/ui/OperationElem.tsx index 1dabeb650..133778c8d 100644 --- a/src/Bladeburner/ui/OperationElem.tsx +++ b/src/Bladeburner/ui/OperationElem.tsx @@ -26,7 +26,6 @@ export function OperationElem(props: IProps): React.ReactElement { 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); } diff --git a/src/Bladeburner/ui/OperationList.tsx b/src/Bladeburner/ui/OperationList.tsx index bff9cc3b7..d78d46351 100644 --- a/src/Bladeburner/ui/OperationList.tsx +++ b/src/Bladeburner/ui/OperationList.tsx @@ -11,13 +11,6 @@ interface IProps { } export function OperationList(props: IProps): React.ReactElement { - const setRerender = useState(false)[1]; - - useEffect(() => { - const id = setInterval(() => setRerender(old => !old), 1000); - return () => clearInterval(id); - }, []); - const names = Object.keys(props.bladeburner.operations); const operations = props.bladeburner.operations; return (<> diff --git a/src/Bladeburner/ui/SkillElem.tsx b/src/Bladeburner/ui/SkillElem.tsx index af7ebfbf1..44312e413 100644 --- a/src/Bladeburner/ui/SkillElem.tsx +++ b/src/Bladeburner/ui/SkillElem.tsx @@ -1,10 +1,11 @@ -import * as React from "react"; +import React, { useState } from "react"; import { CopyableText } from "../../ui/React/CopyableText"; import { formatNumber } from "../../../utils/StringHelperFunctions"; interface IProps { skill: any; bladeburner: any; + onUpgrade: () => void; } export function SkillElem(props: IProps): React.ReactElement { @@ -22,7 +23,7 @@ export function SkillElem(props: IProps): React.ReactElement { if (props.bladeburner.skillPoints < pointCost) return; props.bladeburner.skillPoints -= pointCost; props.bladeburner.upgradeSkill(props.skill); - props.bladeburner.createActionAndSkillsContent(); + props.onUpgrade(); } return (<> diff --git a/src/Bladeburner/ui/SkillList.tsx b/src/Bladeburner/ui/SkillList.tsx index 014b6f992..a7fbe2182 100644 --- a/src/Bladeburner/ui/SkillList.tsx +++ b/src/Bladeburner/ui/SkillList.tsx @@ -4,13 +4,14 @@ import { Skills } from "../Skills"; interface IProps { bladeburner: any; + onUpgrade: () => void; } export function SkillList(props: IProps): React.ReactElement { return (<> {Object.keys(Skills).map((skill: string) =>
  • - +
  • )} ); diff --git a/src/Bladeburner/ui/SkillPage.tsx b/src/Bladeburner/ui/SkillPage.tsx index 8a6e7e9bc..c465e95cc 100644 --- a/src/Bladeburner/ui/SkillPage.tsx +++ b/src/Bladeburner/ui/SkillPage.tsx @@ -1,4 +1,4 @@ -import * as React from "react"; +import React, { useState } from "react"; import { SkillList } from "./SkillList"; import { BladeburnerConstants } from "../data/Constants"; import { formatNumber } from "../../../utils/StringHelperFunctions"; @@ -9,6 +9,7 @@ interface IProps { export function SkillPage(props: IProps): React.ReactElement { + const setRerender = useState(false)[1]; const mults = props.bladeburner.skillMultipliers; function valid(mult: any) { @@ -16,7 +17,9 @@ export function SkillPage(props: IProps): React.ReactElement { } return (<> - Skill Points: {formatNumber(props.bladeburner.skillPoints, 0)} +

    + Skill Points: {formatNumber(props.bladeburner.skillPoints, 0)} +

    You will gain one skill point every {BladeburnerConstants.RanksPerSkillPoint} ranks.
    @@ -24,27 +27,27 @@ export function SkillPage(props: IProps): React.ReactElement { Note that when upgrading a skill, the benefit for that skill is additive. However, the effects of different skills with each other is multiplicative.
    -

    - {valid(mults["successChanceAll"]) && <>Total Success Chance: x{formatNumber(mults["successChanceAll"], 3)}
    } - {valid(mults["successChanceStealth"]) && <>Stealth Success Chance: x{formatNumber(mults["successChanceStealth"], 3)}
    } - {valid(mults["successChanceKill"]) && <>Retirement Success Chance: x{formatNumber(mults["successChanceKill"], 3)}
    } - {valid(mults["successChanceContract"]) && <>Contract Success Chance: x{formatNumber(mults["successChanceContract"], 3)}
    } - {valid(mults["successChanceOperation"]) && <>Operation Success Chance: x{formatNumber(mults["successChanceOperation"], 3)}
    } - {valid(mults["successChanceEstimate"]) && <>Synthoid Data Estimate: x{formatNumber(mults["successChanceEstimate"], 3)}
    } - {valid(mults["actionTime"]) && <>Action Time: x{formatNumber(mults["actionTime"], 3)}
    } - {valid(mults["effHack"]) && <>Hacking Skill: x{formatNumber(mults["effHack"], 3)}
    } - {valid(mults["effStr"]) && <>Strength: x{formatNumber(mults["effStr"], 3)}
    } - {valid(mults["effDef"]) && <>Defense: x{formatNumber(mults["effDef"], 3)}
    } - {valid(mults["effDex"]) && <>Dexterity: x{formatNumber(mults["effDex"], 3)}
    } - {valid(mults["effAgi"]) && <>Agility: x{formatNumber(mults["effAgi"], 3)}
    } - {valid(mults["effCha"]) && <>Charisma: x{formatNumber(mults["effCha"], 3)}
    } - {valid(mults["effInt"]) && <>Intelligence: x{formatNumber(mults["effInt"], 3)}
    } - {valid(mults["stamina"]) && <>Stamina: x{formatNumber(mults["stamina"], 3)}
    } - {valid(mults["money"]) && <>Contract Money: x{formatNumber(mults["money"], 3)}
    } - {valid(mults["expGain"]) && <>Exp Gain: x{formatNumber(mults["expGain"], 3)}
    }
    - + {valid(mults["successChanceAll"]) &&

    Total Success Chance: x{formatNumber(mults["successChanceAll"], 3)}

    } + {valid(mults["successChanceStealth"]) &&

    Stealth Success Chance: x{formatNumber(mults["successChanceStealth"], 3)}

    } + {valid(mults["successChanceKill"]) &&

    Retirement Success Chance: x{formatNumber(mults["successChanceKill"], 3)}

    } + {valid(mults["successChanceContract"]) &&

    Contract Success Chance: x{formatNumber(mults["successChanceContract"], 3)}

    } + {valid(mults["successChanceOperation"]) &&

    Operation Success Chance: x{formatNumber(mults["successChanceOperation"], 3)}

    } + {valid(mults["successChanceEstimate"]) &&

    Synthoid Data Estimate: x{formatNumber(mults["successChanceEstimate"], 3)}

    } + {valid(mults["actionTime"]) &&

    Action Time: x{formatNumber(mults["actionTime"], 3)}

    } + {valid(mults["effHack"]) &&

    Hacking Skill: x{formatNumber(mults["effHack"], 3)}

    } + {valid(mults["effStr"]) &&

    Strength: x{formatNumber(mults["effStr"], 3)}

    } + {valid(mults["effDef"]) &&

    Defense: x{formatNumber(mults["effDef"], 3)}

    } + {valid(mults["effDex"]) &&

    Dexterity: x{formatNumber(mults["effDex"], 3)}

    } + {valid(mults["effAgi"]) &&

    Agility: x{formatNumber(mults["effAgi"], 3)}

    } + {valid(mults["effCha"]) &&

    Charisma: x{formatNumber(mults["effCha"], 3)}

    } + {valid(mults["effInt"]) &&

    Intelligence: x{formatNumber(mults["effInt"], 3)}

    } + {valid(mults["stamina"]) &&

    Stamina: x{formatNumber(mults["stamina"], 3)}

    } + {valid(mults["money"]) &&

    Contract Money: x{formatNumber(mults["money"], 3)}

    } + {valid(mults["expGain"]) &&

    Exp Gain: x{formatNumber(mults["expGain"], 3)}

    } +
    + setRerender(old => !old)} /> ); } diff --git a/src/Bladeburner/ui/Stats.tsx b/src/Bladeburner/ui/Stats.tsx index 9e072f189..e766d8c11 100644 --- a/src/Bladeburner/ui/Stats.tsx +++ b/src/Bladeburner/ui/Stats.tsx @@ -52,27 +52,104 @@ export function Stats(props: IProps): React.ReactElement { "be logged in the Bladeburner Console."); } - return (

    - Rank: {formatNumber(props.bladeburner.rank, 2)}
    - Stamina: {formatNumber(props.bladeburner.stamina, 3)} / {formatNumber(props.bladeburner.maxStamina, 3)} + function openTravel() { + // var popupId = "bladeburner-travel-popup-cancel-btn"; + // var popupArguments = []; + // popupArguments.push(createElement("a", { // Cancel Button + // innerText:"Cancel", class:"a-link-button", + // clickListener:() => { + // removeElementById(popupId); return false; + // }, + // })) + // popupArguments.push(createElement("p", { // Info Text + // innerText:"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", + // })); + // for (var i = 0; i < BladeburnerConstants.CityNames.length; ++i) { + // (function(inst, i) { + // popupArguments.push(createElement("div", { + // // Reusing this css class...it adds a border and makes it + // // so that background color changes when you hover + // class:"cmpy-mgmt-find-employee-option", + // innerText:BladeburnerConstants.CityNames[i], + // clickListener:() => { + // inst.city = BladeburnerConstants.CityNames[i]; + // removeElementById(popupId); + // inst.updateOverviewContent(); + // return false; + // }, + // })); + // })(this, i); + // } + // createPopup(popupId, popupArguments); + } + + function openFaction() { + // if (bladeburnerFac.isMember) { + // Engine.loadFactionContent(); + // displayFactionContent(bladeburnersFactionName); + // } else { + // if (this.rank >= BladeburnerConstants.RankNeededForFaction) { + // joinFaction(bladeburnerFac); + // dialogBoxCreate("Congratulations! You were accepted into the Bladeburners faction"); + // removeChildrenFromElement(DomElems.overviewDiv); + // this.createOverviewContent(); + // } else { + // dialogBoxCreate("You need a rank of 25 to join the Bladeburners Faction!") + // } + // } + } + + return (<> +

    + Rank: {formatNumber(props.bladeburner.rank, 2)} + Your rank within the Bladeburner division. +


    +

    Stamina: {formatNumber(props.bladeburner.stamina, 3)} / {formatNumber(props.bladeburner.maxStamina, 3)}

    ?

    - Est. Synthoid Population: {numeralWrapper.formatPopulation(props.bladeburner.getCurrentCity().popEst)} +

    Stamina Penalty: {formatNumber((1-props.bladeburner.calculateStaminaPenalty())*100, 1)}%


    +

    Team Size: {formatNumber(props.bladeburner.teamSize, 0)}

    +

    Team Members Lost: {formatNumber(props.bladeburner.teamLost, 0)}


    +

    Num Times Hospitalized: {props.bladeburner.numHosp}

    +

    Money Lost From Hospitalizations: {Money(props.bladeburner.moneyLost)}


    +

    Current City: {props.bladeburner.city}

    +

    + Est. Synthoid Population: {numeralWrapper.formatPopulation(props.bladeburner.getCurrentCity().popEst)} + This is your Bladeburner division's estimate of how many Synthoids exist in your current city. +

    ?

    - Est. Synthoid Communities: {formatNumber(props.bladeburner.getCurrentCity().comms, 0)}
    - City Chaos: {formatNumber(props.bladeburner.getCurrentCity().chaos)}
    - Skill Points: {formatNumber(props.bladeburner.skillPoints, 0)}
    - Bonus time: {convertTimeMsToTimeElapsedString(props.bladeburner.storedCycles/BladeburnerConstants.CyclesPerSecond*1000)}
    - Stamina Penalty: {formatNumber((1-props.bladeburner.calculateStaminaPenalty())*100, 1)}%

    - Team Size: {formatNumber(props.bladeburner.teamSize, 0)}
    - Team Members Lost: {formatNumber(props.bladeburner.teamLost, 0)}

    - Num Times Hospitalized: {props.bladeburner.numHosp}
    - Money Lost From Hospitalizations: {Money(props.bladeburner.moneyLost)}

    - Current City: {props.bladeburner.city}
    +

    + Est. Synthoid Communities: {formatNumber(props.bladeburner.getCurrentCity().comms, 0)} + This is your Bladeburner divison's estimate of how many Synthoid communities exist in your current city. +


    +

    + City Chaos: {formatNumber(props.bladeburner.getCurrentCity().chaos)} + 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. +


    +
    +

    + Bonus time: {convertTimeMsToTimeElapsedString(props.bladeburner.storedCycles/BladeburnerConstants.CyclesPerSecond*1000)}
    + + 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. + +

    +

    Skill Points: {formatNumber(props.bladeburner.skillPoints, 0)}


    {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) + "%"], ])} -

    ); -} \ No newline at end of file +
    + Travel + + Apply to the Bladeburner Faction, or go to the faction page if you are already a member + Faction + +
    +
    + ); +} From 99d4f17cdbead2f4a522229803ed377295fb8c6e Mon Sep 17 00:00:00 2001 From: Olivier Gagnon Date: Sun, 15 Aug 2021 21:49:08 -0400 Subject: [PATCH 04/15] work on blade to react --- src/Augmentation/Augmentation.tsx | 2 +- src/Bladeburner.jsx | 149 +---------------------- src/Bladeburner/ui/BlackOpElem.tsx | 43 ++----- src/Bladeburner/ui/BlackOpList.tsx | 8 +- src/Bladeburner/ui/Console.tsx | 108 +++++++++++++--- src/Bladeburner/ui/ContractList.tsx | 5 +- src/Bladeburner/ui/GeneralActionList.tsx | 5 +- src/Bladeburner/ui/OperationElem.tsx | 42 ++----- src/Bladeburner/ui/OperationList.tsx | 5 +- src/Bladeburner/ui/Root.tsx | 27 ++++ src/Bladeburner/ui/SkillList.tsx | 5 +- src/Bladeburner/ui/Stats.tsx | 71 ++++------- src/Bladeburner/ui/TeamSizePopup.tsx | 37 ++++++ src/Bladeburner/ui/TravelPopup.tsx | 31 +++++ src/Faction/ui/FactionList.tsx | 6 +- src/Gang/ui/TaskSelector.tsx | 2 +- utils/StringHelperFunctions.ts | 2 +- 17 files changed, 247 insertions(+), 301 deletions(-) create mode 100644 src/Bladeburner/ui/Root.tsx create mode 100644 src/Bladeburner/ui/TeamSizePopup.tsx create mode 100644 src/Bladeburner/ui/TravelPopup.tsx diff --git a/src/Augmentation/Augmentation.tsx b/src/Augmentation/Augmentation.tsx index d64dedc90..eeb6d2cbf 100644 --- a/src/Augmentation/Augmentation.tsx +++ b/src/Augmentation/Augmentation.tsx @@ -55,7 +55,7 @@ interface IConstructorParams { } function generateStatsDescription(mults: IMap, 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%"; diff --git a/src/Bladeburner.jsx b/src/Bladeburner.jsx index 4693f99b2..8d3b8e158 100644 --- a/src/Bladeburner.jsx +++ b/src/Bladeburner.jsx @@ -52,24 +52,10 @@ 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 { SkillList } from "./Bladeburner/ui/SkillList"; -import { BlackOpElem } from "./Bladeburner/ui/BlackOpElem"; -import { BlackOpList } from "./Bladeburner/ui/BlackOpList"; -import { OperationElem } from "./Bladeburner/ui/OperationElem"; -import { OperationList } from "./Bladeburner/ui/OperationList"; -import { ContractElem } from "./Bladeburner/ui/ContractElem"; -import { ContractList } from "./Bladeburner/ui/ContractList"; -import { GeneralActionElem } from "./Bladeburner/ui/GeneralActionElem"; -import { GeneralActionList } from "./Bladeburner/ui/GeneralActionList"; -import { GeneralActionPage } from "./Bladeburner/ui/GeneralActionPage"; -import { ContractPage } from "./Bladeburner/ui/ContractPage"; -import { OperationPage } from "./Bladeburner/ui/OperationPage"; -import { BlackOpPage } from "./Bladeburner/ui/BlackOpPage"; -import { SkillPage } from "./Bladeburner/ui/SkillPage"; import { Stats } from "./Bladeburner/ui/Stats"; import { AllPages } from "./Bladeburner/ui/AllPages"; import { Console } from "./Bladeburner/ui/Console"; +import { Root } from "./Bladeburner/ui/Root"; import { StatsTable } from "./ui/React/StatsTable"; import { CopyableText } from "./ui/React/CopyableText"; @@ -77,73 +63,6 @@ import { Money } from "./ui/React/Money"; import React from "react"; import ReactDOM from "react-dom"; -const stealthIcon = ` ` -const killIcon = `` - -// DOM related variables -const ActiveActionCssClass = "bladeburner-active-action"; - -// Console related stuff -let consoleHistoryIndex = 0; - -// Keypresses for Console -$(document).keydown(function(event) { - if (routing.isOn(Page.Bladeburner)) { - if (!(Player.bladeburner instanceof Bladeburner)) { return; } - let consoleHistory = Player.bladeburner.consoleHistory; - - if (event.keyCode === KEY.ENTER) { - event.preventDefault(); - var command = DomElems.consoleInput.value; - if (command.length > 0) { - Player.bladeburner.postToConsole("> " + command); - Player.bladeburner.resetConsoleInput(); - Player.bladeburner.executeConsoleCommands(command); - } - } - - if (event.keyCode === KEY.UPARROW) { - if (DomElems.consoleInput == null) {return;} - var i = consoleHistoryIndex; - var len = consoleHistory.length; - - if (len === 0) {return;} - if (i < 0 || i > len) { - consoleHistoryIndex = len; - } - - if (i !== 0) { - --consoleHistoryIndex; - } - - var prevCommand = consoleHistory[consoleHistoryIndex]; - DomElems.consoleInput.value = prevCommand; - setTimeoutRef(function(){DomElems.consoleInput.selectionStart = DomElems.consoleInput.selectionEnd = 10000; }, 0); - } - - if (event.keyCode === KEY.DOWNARROW) { - if (DomElems.consoleInput == null) {return;} - var i = consoleHistoryIndex; - var len = consoleHistory.length; - - if (len == 0) {return;} - if (i < 0 || i > len) { - consoleHistoryIndex = len; - } - - // Latest command, put nothing - if (i == len || i == len-1) { - consoleHistoryIndex = len; - DomElems.consoleInput.value = ""; - } else { - ++consoleHistoryIndex; - var prevCommand = consoleHistory[consoleHistoryIndex]; - DomElems.consoleInput.value = prevCommand; - } - } - } -}); - function ActionIdentifier(params={}) { if (params.name) {this.name = params.name;} if (params.type) {this.type = params.type;} @@ -1216,67 +1135,19 @@ let DomElems = {}; Bladeburner.prototype.initializeDomElementRefs = function() { DomElems = { - bladeburnerDiv: null, - - // Main Divs - overviewConsoleParentDiv: null, - - overviewDiv: null, // Overview of stats that stays fixed on left - actionAndSkillsDiv: null, // Panel for different sections (contracts, ops, skills) - - consoleDiv: null, - consoleTable: null, - consoleInputRow: null, // tr - consoleInputCell: null, // td - consoleInputHeader: null, // "> " - consoleInput: null, // Actual input element + bladeburnerDiv: null, }; } Bladeburner.prototype.createContent = function() { - DomElems.bladeburnerDiv = createElement("div", { - id:"bladeburner-container", position:"fixed", class:"generic-menupage-container", - }); + DomElems.bladeburnerDiv = createElement("div"); - // Parent Div for Overview and Console - DomElems.overviewConsoleParentDiv = createElement("div", { - height:"60%", display:"block", position:"relative", - }); - - // Overview and Action/Skill pane - DomElems.overviewDiv = createElement("div", { - width:"30%", display:"inline-block", border:"1px solid white", - }); - - DomElems.actionAndSkillsDiv = createElement("div", { - width:"70%", display:"block", - border:"1px solid white", margin:"6px", padding:"6px", - }); - - ReactDOM.render(, DomElems.overviewDiv); - ReactDOM.render(, DomElems.actionAndSkillsDiv); - - // Console - DomElems.consoleDiv = createElement("div", { - class:"bladeburner-console-div", - clickListener:() => { - if (DomElems.consoleInput instanceof Element) { - DomElems.consoleInput.focus(); - } - return false; - }, - }); - ReactDOM.render(, DomElems.consoleDiv); - - DomElems.overviewConsoleParentDiv.appendChild(DomElems.overviewDiv); - DomElems.overviewConsoleParentDiv.appendChild(DomElems.consoleDiv); - DomElems.bladeburnerDiv.appendChild(DomElems.overviewConsoleParentDiv); - DomElems.bladeburnerDiv.appendChild(DomElems.actionAndSkillsDiv); + ReactDOM.render(, DomElems.bladeburnerDiv); document.getElementById("entire-game-container").appendChild(DomElems.bladeburnerDiv); if (this.consoleLogs.length === 0) { - this.postToConsole("Bladeburner Console BETA"); + this.postToConsole("Bladeburner Console"); this.postToConsole("Type 'help' to see console commands"); } else { for (let i = 0; i < this.consoleLogs.length; ++i) { @@ -1323,16 +1194,7 @@ Bladeburner.prototype.postToConsole = function(input, saveToLogs=true) { } } - -Bladeburner.prototype.resetConsoleInput = function() { - DomElems.consoleInput.value = ""; -} - Bladeburner.prototype.clearConsole = function() { - while (DomElems.consoleTable.childNodes.length > 1) { - DomElems.consoleTable.removeChild(DomElems.consoleTable.firstChild); - } - this.consoleLogs.length = 0; } @@ -1351,7 +1213,6 @@ Bladeburner.prototype.executeConsoleCommands = function(commands) { this.consoleHistory.splice(0, 1); } } - consoleHistoryIndex = this.consoleHistory.length; const arrayOfCommands = commands.split(";"); for (let i = 0; i < arrayOfCommands.length; ++i) { diff --git a/src/Bladeburner/ui/BlackOpElem.tsx b/src/Bladeburner/ui/BlackOpElem.tsx index 3fd3268f9..d2afc6e22 100644 --- a/src/Bladeburner/ui/BlackOpElem.tsx +++ b/src/Bladeburner/ui/BlackOpElem.tsx @@ -6,6 +6,8 @@ import { 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"; interface IProps { bladeburner: any; @@ -34,41 +36,12 @@ export function BlackOpElem(props: IProps): React.ReactElement { } 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(); + const popupId = "bladeburner-operation-set-team-size-popup"; + createPopup(popupId, TeamSizePopup, { + bladeburner: props.bladeburner, + action: props.action, + popupId: popupId, + }); } return (<> diff --git a/src/Bladeburner/ui/BlackOpList.tsx b/src/Bladeburner/ui/BlackOpList.tsx index eeb0f9907..542eecaab 100644 --- a/src/Bladeburner/ui/BlackOpList.tsx +++ b/src/Bladeburner/ui/BlackOpList.tsx @@ -25,18 +25,16 @@ export function BlackOpList(props: IProps): React.ReactElement { return (a.reqdRank - b.reqdRank); }); - blackops = blackops.filter((blackop: BlackOperation, i: number) => - !(props.bladeburner.blackops[blackops[i].name] == null && + 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) => -
  • + {blackops.map((blackop: BlackOperation) =>
  • -
  • + , )} ); } \ No newline at end of file diff --git a/src/Bladeburner/ui/Console.tsx b/src/Bladeburner/ui/Console.tsx index 7c4de7eb8..971af3c25 100644 --- a/src/Bladeburner/ui/Console.tsx +++ b/src/Bladeburner/ui/Console.tsx @@ -15,29 +15,97 @@ interface IProps { } export function Console(props: IProps): React.ReactElement { - const lastRef = useRef(null); + const lastRef = useRef(null); const setRerender = useState(false)[1]; - useEffect(() => { + 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.scrollIntoView({block: "end", inline: "nearest", behavior: "smooth" }); - const id = setInterval(() => setRerender(old => !old), 1000); - return () => clearInterval(id); + 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); + }; }, []); - return ( - - {/* - 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) => )} - - - - -
    -
    {"> "}
    -
    ); + function handleKeyDown(event: React.KeyboardEvent): 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(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 (
    + + + {/* + 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) => )} + + + + +
    +
    {"> "}
    +
    +
    ); } \ No newline at end of file diff --git a/src/Bladeburner/ui/ContractList.tsx b/src/Bladeburner/ui/ContractList.tsx index dcea7ea5e..6ba3b53f2 100644 --- a/src/Bladeburner/ui/ContractList.tsx +++ b/src/Bladeburner/ui/ContractList.tsx @@ -14,10 +14,9 @@ export function ContractList(props: IProps): React.ReactElement { const names = Object.keys(props.bladeburner.contracts); const contracts = props.bladeburner.contracts; return (<> - {names.map((name: string) => -
  • + {names.map((name: string) =>
  • -
  • + , )} ); } \ No newline at end of file diff --git a/src/Bladeburner/ui/GeneralActionList.tsx b/src/Bladeburner/ui/GeneralActionList.tsx index e459899d4..a099ae8e6 100644 --- a/src/Bladeburner/ui/GeneralActionList.tsx +++ b/src/Bladeburner/ui/GeneralActionList.tsx @@ -19,10 +19,9 @@ export function GeneralActionList(props: IProps): React.ReactElement { } } return (<> - {actions.map((action: Action) => -
  • + {actions.map((action: Action) =>
  • -
  • + , )} ); } \ No newline at end of file diff --git a/src/Bladeburner/ui/OperationElem.tsx b/src/Bladeburner/ui/OperationElem.tsx index 133778c8d..e4ac63898 100644 --- a/src/Bladeburner/ui/OperationElem.tsx +++ b/src/Bladeburner/ui/OperationElem.tsx @@ -7,6 +7,8 @@ import { } from "../../../utils/StringHelperFunctions"; import { stealthIcon, killIcon } from "../data/Icons"; import { BladeburnerConstants } from "../data/Constants"; +import { createPopup } from "../../ui/React/createPopup"; +import { TeamSizePopup } from "./TeamSizePopup"; interface IProps { bladeburner: any; @@ -30,40 +32,12 @@ export function OperationElem(props: IProps): React.ReactElement { } 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(); + const popupId = "bladeburner-operation-set-team-size-popup"; + createPopup(popupId, TeamSizePopup, { + bladeburner: props.bladeburner, + action: props.action, + popupId: popupId, + }); } function increaseLevel() { diff --git a/src/Bladeburner/ui/OperationList.tsx b/src/Bladeburner/ui/OperationList.tsx index d78d46351..73d07cb16 100644 --- a/src/Bladeburner/ui/OperationList.tsx +++ b/src/Bladeburner/ui/OperationList.tsx @@ -14,10 +14,9 @@ export function OperationList(props: IProps): React.ReactElement { const names = Object.keys(props.bladeburner.operations); const operations = props.bladeburner.operations; return (<> - {names.map((name: string) => -
  • + {names.map((name: string) =>
  • -
  • + , )} ); } \ No newline at end of file diff --git a/src/Bladeburner/ui/Root.tsx b/src/Bladeburner/ui/Root.tsx new file mode 100644 index 000000000..1c1f37106 --- /dev/null +++ b/src/Bladeburner/ui/Root.tsx @@ -0,0 +1,27 @@ +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"; + +interface IProps { + bladeburner: any; + engine: IEngine; + player: IPlayer; +} + +export function Root(props: IProps): React.ReactElement { + return (
    +
    +
    + +
    + +
    +
    + +
    +
    ); +} \ No newline at end of file diff --git a/src/Bladeburner/ui/SkillList.tsx b/src/Bladeburner/ui/SkillList.tsx index a7fbe2182..ca55033d0 100644 --- a/src/Bladeburner/ui/SkillList.tsx +++ b/src/Bladeburner/ui/SkillList.tsx @@ -9,10 +9,9 @@ interface IProps { export function SkillList(props: IProps): React.ReactElement { return (<> - {Object.keys(Skills).map((skill: string) => -
  • + {Object.keys(Skills).map((skill: string) =>
  • -
  • + , )} ); } \ No newline at end of file diff --git a/src/Bladeburner/ui/Stats.tsx b/src/Bladeburner/ui/Stats.tsx index e766d8c11..6a6899869 100644 --- a/src/Bladeburner/ui/Stats.tsx +++ b/src/Bladeburner/ui/Stats.tsx @@ -5,13 +5,23 @@ import { } 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 { TravelPopup } from "./TravelPopup"; interface IProps { bladeburner: any; + engine: IEngine; player: IPlayer; } @@ -53,53 +63,26 @@ export function Stats(props: IProps): React.ReactElement { } function openTravel() { - // var popupId = "bladeburner-travel-popup-cancel-btn"; - // var popupArguments = []; - // popupArguments.push(createElement("a", { // Cancel Button - // innerText:"Cancel", class:"a-link-button", - // clickListener:() => { - // removeElementById(popupId); return false; - // }, - // })) - // popupArguments.push(createElement("p", { // Info Text - // innerText:"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", - // })); - // for (var i = 0; i < BladeburnerConstants.CityNames.length; ++i) { - // (function(inst, i) { - // popupArguments.push(createElement("div", { - // // Reusing this css class...it adds a border and makes it - // // so that background color changes when you hover - // class:"cmpy-mgmt-find-employee-option", - // innerText:BladeburnerConstants.CityNames[i], - // clickListener:() => { - // inst.city = BladeburnerConstants.CityNames[i]; - // removeElementById(popupId); - // inst.updateOverviewContent(); - // return false; - // }, - // })); - // })(this, i); - // } - // createPopup(popupId, popupArguments); + const popupId = "bladeburner-travel-popup"; + createPopup(popupId, TravelPopup, { + bladeburner: props.bladeburner, + popupId: popupId, + }); } function openFaction() { - // if (bladeburnerFac.isMember) { - // Engine.loadFactionContent(); - // displayFactionContent(bladeburnersFactionName); - // } else { - // if (this.rank >= BladeburnerConstants.RankNeededForFaction) { - // joinFaction(bladeburnerFac); - // dialogBoxCreate("Congratulations! You were accepted into the Bladeburners faction"); - // removeChildrenFromElement(DomElems.overviewDiv); - // this.createOverviewContent(); - // } else { - // dialogBoxCreate("You need a rank of 25 to join the Bladeburners Faction!") - // } - // } + 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 (<> diff --git a/src/Bladeburner/ui/TeamSizePopup.tsx b/src/Bladeburner/ui/TeamSizePopup.tsx new file mode 100644 index 000000000..4eb9b07db --- /dev/null +++ b/src/Bladeburner/ui/TeamSizePopup.tsx @@ -0,0 +1,37 @@ +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"; + +interface IProps { + bladeburner: any; + action: Action; + popupId: string; +} + +export function TeamSizePopup(props: IProps): React.ReactElement { + const [teamSize, setTeamSize] = useState(); + + 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 (<> +

    + 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. +

    + setTeamSize(parseFloat(event.target.value))} /> + Confirm + ); +} \ No newline at end of file diff --git a/src/Bladeburner/ui/TravelPopup.tsx b/src/Bladeburner/ui/TravelPopup.tsx new file mode 100644 index 000000000..3becd7cc4 --- /dev/null +++ b/src/Bladeburner/ui/TravelPopup.tsx @@ -0,0 +1,31 @@ +import React from "react"; +import { removePopup } from "../../ui/React/createPopup"; +import { BladeburnerConstants } from "../data/Constants"; + +interface IProps { + bladeburner: any; + popupId: string; +} + +export function TravelPopup(props: IProps): React.ReactElement { + function travel(city: string) { + props.bladeburner.city = city; + removePopup(props.popupId); + } + + return (<> +

    + 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. +

    + {BladeburnerConstants.CityNames.map(city => + // Reusing this css class...it adds a border and makes it + // so that background color changes when you hover +
    travel(city)}> + {city} +
    )} + ); +} \ No newline at end of file diff --git a/src/Faction/ui/FactionList.tsx b/src/Faction/ui/FactionList.tsx index 3b001a4a3..15a6bda48 100644 --- a/src/Faction/ui/FactionList.tsx +++ b/src/Faction/ui/FactionList.tsx @@ -28,8 +28,7 @@ export function FactionList(props: IProps): React.ReactElement {

    Lists all factions you have joined


      - {props.player.factions.map((faction: string) => -
    • openFaction(faction)} style={{padding:"4px", margin:"4px", display:"inline-block"}}>{faction} @@ -39,8 +38,7 @@ export function FactionList(props: IProps): React.ReactElement {

      Outstanding Faction Invitations

      Lists factions you have been invited to. You can accept these faction invitations at any time.

        - {props.player.factionInvitations.map((faction: string) => -
      • + {props.player.factionInvitations.map((faction: string) =>
      • {faction}

        --- {tasks.map((task: string, i: number) => )} -
        {StatsTable(data, null)}
        +
        {StatsTable(data)}
        ); } \ No newline at end of file diff --git a/utils/StringHelperFunctions.ts b/utils/StringHelperFunctions.ts index 2cde62156..4eb88058a 100644 --- a/utils/StringHelperFunctions.ts +++ b/utils/StringHelperFunctions.ts @@ -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 = 0): string { +function formatNumber(num: number, numFractionDigits = 0): string { return num.toLocaleString(undefined, { maximumFractionDigits: numFractionDigits, minimumFractionDigits: numFractionDigits, From ae6f95b59a4655e36e386dca48ab84af3b6755c1 Mon Sep 17 00:00:00 2001 From: Olivier Gagnon Date: Sun, 15 Aug 2021 22:35:43 -0400 Subject: [PATCH 05/15] The blade UI is fully converted to React, the business logic is left to do. --- src/Bladeburner.jsx | 74 ++---------------------- src/Bladeburner/ActionIdentifier.ts | 27 +++++++++ src/Bladeburner/IActionIdentifier.ts | 2 +- src/Bladeburner/IBladeburner.ts | 6 ++ src/Bladeburner/ui/AllPages.tsx | 3 +- src/Bladeburner/ui/BlackOpElem.tsx | 3 +- src/Bladeburner/ui/BlackOpList.tsx | 3 +- src/Bladeburner/ui/BlackOpPage.tsx | 3 +- src/Bladeburner/ui/Console.tsx | 5 +- src/Bladeburner/ui/ContractElem.tsx | 3 +- src/Bladeburner/ui/ContractList.tsx | 3 +- src/Bladeburner/ui/ContractPage.tsx | 3 +- src/Bladeburner/ui/GeneralActionElem.tsx | 3 +- src/Bladeburner/ui/GeneralActionList.tsx | 3 +- src/Bladeburner/ui/GeneralActionPage.tsx | 3 +- src/Bladeburner/ui/OperationElem.tsx | 3 +- src/Bladeburner/ui/OperationList.tsx | 3 +- src/Bladeburner/ui/OperationPage.tsx | 3 +- src/Bladeburner/ui/Root.tsx | 5 +- src/Bladeburner/ui/SkillElem.tsx | 3 +- src/Bladeburner/ui/SkillList.tsx | 3 +- src/Bladeburner/ui/SkillPage.tsx | 3 +- src/Bladeburner/ui/Stats.tsx | 3 +- src/Bladeburner/ui/TeamSizePopup.tsx | 3 +- src/Bladeburner/ui/TravelPopup.tsx | 3 +- src/engine.jsx | 31 +++++----- src/index.html | 6 +- 27 files changed, 104 insertions(+), 109 deletions(-) create mode 100644 src/Bladeburner/ActionIdentifier.ts diff --git a/src/Bladeburner.jsx b/src/Bladeburner.jsx index 8d3b8e158..72449b356 100644 --- a/src/Bladeburner.jsx +++ b/src/Bladeburner.jsx @@ -36,6 +36,7 @@ import { BlackOperations } from "./Bladeburner/BlackOperations"; import { Contract } from "./Bladeburner/Contract"; import { GeneralActions } from "./Bladeburner/GeneralActions"; import { ActionTypes } from "./Bladeburner/data/ActionTypes"; +import { ActionIdentifier } from "./Bladeburner/ActionIdentifier"; import { addOffset } from "../utils/helpers/addOffset"; import { clearObject } from "../utils/helpers/clearObject"; @@ -63,21 +64,6 @@ import { Money } from "./ui/React/Money"; import React from "react"; import ReactDOM from "react-dom"; -function ActionIdentifier(params={}) { - if (params.name) {this.name = params.name;} - if (params.type) {this.type = params.type;} -} - -ActionIdentifier.prototype.toJSON = function() { - return Generic_toJSON("ActionIdentifier", this); -} - -ActionIdentifier.fromJSON = function(value) { - return Generic_fromJSON(ActionIdentifier, value.data); -} - -Reviver.constructors.ActionIdentifier = ActionIdentifier; - function Bladeburner(params={}) { this.numHosp = 0; // Number of hospitalizations this.moneyLost = 0; // Money lost due to hospitalizations @@ -149,10 +135,12 @@ function Bladeburner(params={}) { // Console command history this.consoleHistory = []; - this.consoleLogs = []; + this.consoleLogs = [ + "Bladeburner Console", + "Type 'help' to see console commands", + ]; // Initialization - this.initializeDomElementRefs(); if (params.new) {this.create();} } @@ -1131,58 +1119,6 @@ Bladeburner.prototype.triggerMigration = function(sourceCityName) { destCity.pop += count; } -let DomElems = {}; - -Bladeburner.prototype.initializeDomElementRefs = function() { - DomElems = { - bladeburnerDiv: null, - }; -} - -Bladeburner.prototype.createContent = function() { - DomElems.bladeburnerDiv = createElement("div"); - - ReactDOM.render(, DomElems.bladeburnerDiv); - - document.getElementById("entire-game-container").appendChild(DomElems.bladeburnerDiv); - - if (this.consoleLogs.length === 0) { - this.postToConsole("Bladeburner Console"); - this.postToConsole("Type 'help' to see console commands"); - } else { - for (let i = 0; i < this.consoleLogs.length; ++i) { - this.postToConsole(this.consoleLogs[i], false); - } - } -} - -Bladeburner.prototype.clearContent = function() { - if (DomElems.bladeburnerDiv instanceof Element) { - removeChildrenFromElement(DomElems.bladeburnerDiv); - removeElement(DomElems.bladeburnerDiv); - } - clearObject(DomElems); - this.initializeDomElementRefs(); -} - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -///////////////////////////////HYDRO END OF UI////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - // Bladeburner Console Window Bladeburner.prototype.postToConsole = function(input, saveToLogs=true) { const MaxConsoleEntries = 100; diff --git a/src/Bladeburner/ActionIdentifier.ts b/src/Bladeburner/ActionIdentifier.ts new file mode 100644 index 000000000..4358f3ba1 --- /dev/null +++ b/src/Bladeburner/ActionIdentifier.ts @@ -0,0 +1,27 @@ +import { Generic_fromJSON, Generic_toJSON, Reviver } from "../../utils/JSONReviver"; + +interface IParams { + name?: string; + type?: number; +} + +export class ActionIdentifier { + name?: string; + type?: number; + + 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; diff --git a/src/Bladeburner/IActionIdentifier.ts b/src/Bladeburner/IActionIdentifier.ts index 450ba774f..7be44d65c 100644 --- a/src/Bladeburner/IActionIdentifier.ts +++ b/src/Bladeburner/IActionIdentifier.ts @@ -1,4 +1,4 @@ export interface IActionIdentifier { name: string; - type: string; + type: number; } \ No newline at end of file diff --git a/src/Bladeburner/IBladeburner.ts b/src/Bladeburner/IBladeburner.ts index 7a7d9cd0e..debaafcc5 100644 --- a/src/Bladeburner/IBladeburner.ts +++ b/src/Bladeburner/IBladeburner.ts @@ -1,5 +1,6 @@ import { IActionIdentifier } from "./IActionIdentifier"; import { City } from "./City"; +import { Skill } from "./Skill"; export interface IBladeburner { numHosp: number; @@ -14,6 +15,7 @@ export interface IBladeburner { randomEventCounter: number; actionTimeToComplete: number; actionTimeCurrent: number; + actionTimeOverflow: number; action: IActionIdentifier; cities: any; city: string; @@ -36,4 +38,8 @@ export interface IBladeburner { getCurrentCity(): City; calculateStaminaPenalty(): number; + startAction(action: IActionIdentifier): void; + upgradeSkill(skill: Skill): void; + executeConsoleCommands(command: string): void; + postToConsole(input: string, saveToLogs: boolean): void; } \ No newline at end of file diff --git a/src/Bladeburner/ui/AllPages.tsx b/src/Bladeburner/ui/AllPages.tsx index deadaf103..bfdd3466d 100644 --- a/src/Bladeburner/ui/AllPages.tsx +++ b/src/Bladeburner/ui/AllPages.tsx @@ -5,9 +5,10 @@ import { OperationPage } from "./OperationPage"; import { BlackOpPage } from "./BlackOpPage"; import { SkillPage } from "./SkillPage"; import { stealthIcon, killIcon } from "../data/Icons"; +import { IBladeburner } from "../IBladeburner"; interface IProps { - bladeburner: any; + bladeburner: IBladeburner; } export function AllPages(props: IProps): React.ReactElement { diff --git a/src/Bladeburner/ui/BlackOpElem.tsx b/src/Bladeburner/ui/BlackOpElem.tsx index d2afc6e22..5ac713359 100644 --- a/src/Bladeburner/ui/BlackOpElem.tsx +++ b/src/Bladeburner/ui/BlackOpElem.tsx @@ -8,9 +8,10 @@ import { createProgressBarText } from "../../../utils/helpers/createProgressBarT import { stealthIcon, killIcon } from "../data/Icons"; import { createPopup } from "../../ui/React/createPopup"; import { TeamSizePopup } from "./TeamSizePopup"; +import { IBladeburner } from "../IBladeburner"; interface IProps { - bladeburner: any; + bladeburner: IBladeburner; action: any; } diff --git a/src/Bladeburner/ui/BlackOpList.tsx b/src/Bladeburner/ui/BlackOpList.tsx index 542eecaab..75cfafaf1 100644 --- a/src/Bladeburner/ui/BlackOpList.tsx +++ b/src/Bladeburner/ui/BlackOpList.tsx @@ -9,9 +9,10 @@ import { stealthIcon, killIcon } from "../data/Icons"; import { BlackOperations } from "../BlackOperations"; import { BlackOperation } from "../BlackOperation"; import { BlackOpElem } from "./BlackOpElem"; +import { IBladeburner } from "../IBladeburner"; interface IProps { - bladeburner: any; + bladeburner: IBladeburner; } export function BlackOpList(props: IProps): React.ReactElement { diff --git a/src/Bladeburner/ui/BlackOpPage.tsx b/src/Bladeburner/ui/BlackOpPage.tsx index 3682a5398..834b0b327 100644 --- a/src/Bladeburner/ui/BlackOpPage.tsx +++ b/src/Bladeburner/ui/BlackOpPage.tsx @@ -1,8 +1,9 @@ import * as React from "react"; import { BlackOpList } from "./BlackOpList"; +import { IBladeburner } from "../IBladeburner"; interface IProps { - bladeburner: any; + bladeburner: IBladeburner; } export function BlackOpPage(props: IProps): React.ReactElement { diff --git a/src/Bladeburner/ui/Console.tsx b/src/Bladeburner/ui/Console.tsx index 971af3c25..bfe6ded6f 100644 --- a/src/Bladeburner/ui/Console.tsx +++ b/src/Bladeburner/ui/Console.tsx @@ -1,4 +1,5 @@ import React, { useState, useRef, useEffect } from "react"; +import { IBladeburner } from "../IBladeburner"; interface ILineProps { content: any; @@ -11,7 +12,7 @@ function Line(props: ILineProps): React.ReactElement { } interface IProps { - bladeburner: any; + bladeburner: IBladeburner; } export function Console(props: IProps): React.ReactElement { @@ -45,7 +46,7 @@ export function Console(props: IProps): React.ReactElement { const command = event.currentTarget.value; event.currentTarget.value = ""; if (command.length > 0) { - props.bladeburner.postToConsole("> " + command); + props.bladeburner.postToConsole("> " + command, true); props.bladeburner.executeConsoleCommands(command); setConsoleHistoryIndex(props.bladeburner.consoleHistory.length); rerender(); diff --git a/src/Bladeburner/ui/ContractElem.tsx b/src/Bladeburner/ui/ContractElem.tsx index 5ffc7d1e9..568c62cfb 100644 --- a/src/Bladeburner/ui/ContractElem.tsx +++ b/src/Bladeburner/ui/ContractElem.tsx @@ -7,9 +7,10 @@ import { } from "../../../utils/StringHelperFunctions"; import { stealthIcon, killIcon } from "../data/Icons"; import { BladeburnerConstants } from "../data/Constants"; +import { IBladeburner } from "../IBladeburner"; interface IProps { - bladeburner: any; + bladeburner: IBladeburner; action: any; } diff --git a/src/Bladeburner/ui/ContractList.tsx b/src/Bladeburner/ui/ContractList.tsx index 6ba3b53f2..8d3c7bace 100644 --- a/src/Bladeburner/ui/ContractList.tsx +++ b/src/Bladeburner/ui/ContractList.tsx @@ -5,9 +5,10 @@ import { } from "../../../utils/StringHelperFunctions"; import { ContractElem } from "./ContractElem"; import { Contract } from "../Contract"; +import { IBladeburner } from "../IBladeburner"; interface IProps { - bladeburner: any; + bladeburner: IBladeburner; } export function ContractList(props: IProps): React.ReactElement { diff --git a/src/Bladeburner/ui/ContractPage.tsx b/src/Bladeburner/ui/ContractPage.tsx index b64a0b4ae..b151f84c2 100644 --- a/src/Bladeburner/ui/ContractPage.tsx +++ b/src/Bladeburner/ui/ContractPage.tsx @@ -1,8 +1,9 @@ import * as React from "react"; import { ContractList } from "./ContractList"; +import { IBladeburner } from "../IBladeburner"; interface IProps { - bladeburner: any; + bladeburner: IBladeburner; } export function ContractPage(props: IProps): React.ReactElement { diff --git a/src/Bladeburner/ui/GeneralActionElem.tsx b/src/Bladeburner/ui/GeneralActionElem.tsx index 93b7b1e2d..0cadbd7c2 100644 --- a/src/Bladeburner/ui/GeneralActionElem.tsx +++ b/src/Bladeburner/ui/GeneralActionElem.tsx @@ -7,9 +7,10 @@ import { } from "../../../utils/StringHelperFunctions"; import { stealthIcon, killIcon } from "../data/Icons"; import { BladeburnerConstants } from "../data/Constants"; +import { IBladeburner } from "../IBladeburner"; interface IProps { - bladeburner: any; + bladeburner: IBladeburner; action: any; } diff --git a/src/Bladeburner/ui/GeneralActionList.tsx b/src/Bladeburner/ui/GeneralActionList.tsx index a099ae8e6..9a2d4df08 100644 --- a/src/Bladeburner/ui/GeneralActionList.tsx +++ b/src/Bladeburner/ui/GeneralActionList.tsx @@ -6,9 +6,10 @@ import { import { GeneralActionElem } from "./GeneralActionElem"; import { Action } from "../Action"; import { GeneralActions } from "../GeneralActions"; +import { IBladeburner } from "../IBladeburner"; interface IProps { - bladeburner: any; + bladeburner: IBladeburner; } export function GeneralActionList(props: IProps): React.ReactElement { diff --git a/src/Bladeburner/ui/GeneralActionPage.tsx b/src/Bladeburner/ui/GeneralActionPage.tsx index ea1b601f3..bf87173f0 100644 --- a/src/Bladeburner/ui/GeneralActionPage.tsx +++ b/src/Bladeburner/ui/GeneralActionPage.tsx @@ -1,8 +1,9 @@ import * as React from "react"; import { GeneralActionList } from "./GeneralActionList"; +import { IBladeburner } from "../IBladeburner"; interface IProps { - bladeburner: any; + bladeburner: IBladeburner; } export function GeneralActionPage(props: IProps): React.ReactElement { diff --git a/src/Bladeburner/ui/OperationElem.tsx b/src/Bladeburner/ui/OperationElem.tsx index e4ac63898..20e1f2892 100644 --- a/src/Bladeburner/ui/OperationElem.tsx +++ b/src/Bladeburner/ui/OperationElem.tsx @@ -9,9 +9,10 @@ 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"; interface IProps { - bladeburner: any; + bladeburner: IBladeburner; action: any; } diff --git a/src/Bladeburner/ui/OperationList.tsx b/src/Bladeburner/ui/OperationList.tsx index 73d07cb16..5d2387951 100644 --- a/src/Bladeburner/ui/OperationList.tsx +++ b/src/Bladeburner/ui/OperationList.tsx @@ -5,9 +5,10 @@ import { } from "../../../utils/StringHelperFunctions"; import { OperationElem } from "./OperationElem"; import { Operation } from "../Operation"; +import { IBladeburner } from "../IBladeburner"; interface IProps { - bladeburner: any; + bladeburner: IBladeburner; } export function OperationList(props: IProps): React.ReactElement { diff --git a/src/Bladeburner/ui/OperationPage.tsx b/src/Bladeburner/ui/OperationPage.tsx index 3da625131..13233440d 100644 --- a/src/Bladeburner/ui/OperationPage.tsx +++ b/src/Bladeburner/ui/OperationPage.tsx @@ -1,8 +1,9 @@ import * as React from "react"; import { OperationList } from "./OperationList"; +import { IBladeburner } from "../IBladeburner"; interface IProps { - bladeburner: any; + bladeburner: IBladeburner; } export function OperationPage(props: IProps): React.ReactElement { diff --git a/src/Bladeburner/ui/Root.tsx b/src/Bladeburner/ui/Root.tsx index 1c1f37106..0a1ea2881 100644 --- a/src/Bladeburner/ui/Root.tsx +++ b/src/Bladeburner/ui/Root.tsx @@ -5,15 +5,16 @@ import { AllPages } from "./AllPages"; import { IPlayer } from "../../PersonObjects/IPlayer"; import { IEngine } from "../../IEngine"; +import { IBladeburner } from "../IBladeburner"; interface IProps { - bladeburner: any; + bladeburner: IBladeburner; engine: IEngine; player: IPlayer; } export function Root(props: IProps): React.ReactElement { - return (
        + return (
        diff --git a/src/Bladeburner/ui/SkillElem.tsx b/src/Bladeburner/ui/SkillElem.tsx index 44312e413..a7cf6341a 100644 --- a/src/Bladeburner/ui/SkillElem.tsx +++ b/src/Bladeburner/ui/SkillElem.tsx @@ -1,10 +1,11 @@ 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: any; + bladeburner: IBladeburner; onUpgrade: () => void; } diff --git a/src/Bladeburner/ui/SkillList.tsx b/src/Bladeburner/ui/SkillList.tsx index ca55033d0..71c67692d 100644 --- a/src/Bladeburner/ui/SkillList.tsx +++ b/src/Bladeburner/ui/SkillList.tsx @@ -1,9 +1,10 @@ import * as React from "react"; import { SkillElem } from "./SkillElem"; import { Skills } from "../Skills"; +import { IBladeburner } from "../IBladeburner"; interface IProps { - bladeburner: any; + bladeburner: IBladeburner; onUpgrade: () => void; } diff --git a/src/Bladeburner/ui/SkillPage.tsx b/src/Bladeburner/ui/SkillPage.tsx index c465e95cc..48c206a00 100644 --- a/src/Bladeburner/ui/SkillPage.tsx +++ b/src/Bladeburner/ui/SkillPage.tsx @@ -2,9 +2,10 @@ import React, { useState } from "react"; import { SkillList } from "./SkillList"; import { BladeburnerConstants } from "../data/Constants"; import { formatNumber } from "../../../utils/StringHelperFunctions"; +import { IBladeburner } from "../IBladeburner"; interface IProps { - bladeburner: any; + bladeburner: IBladeburner; } diff --git a/src/Bladeburner/ui/Stats.tsx b/src/Bladeburner/ui/Stats.tsx index 6a6899869..073dc7de0 100644 --- a/src/Bladeburner/ui/Stats.tsx +++ b/src/Bladeburner/ui/Stats.tsx @@ -16,11 +16,12 @@ import { joinFaction, displayFactionContent, } from "../../Faction/FactionHelpers"; +import { IBladeburner } from "../IBladeburner"; import { TravelPopup } from "./TravelPopup"; interface IProps { - bladeburner: any; + bladeburner: IBladeburner; engine: IEngine; player: IPlayer; } diff --git a/src/Bladeburner/ui/TeamSizePopup.tsx b/src/Bladeburner/ui/TeamSizePopup.tsx index 4eb9b07db..85973cf9f 100644 --- a/src/Bladeburner/ui/TeamSizePopup.tsx +++ b/src/Bladeburner/ui/TeamSizePopup.tsx @@ -3,9 +3,10 @@ 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: any; + bladeburner: IBladeburner; action: Action; popupId: string; } diff --git a/src/Bladeburner/ui/TravelPopup.tsx b/src/Bladeburner/ui/TravelPopup.tsx index 3becd7cc4..205f3da54 100644 --- a/src/Bladeburner/ui/TravelPopup.tsx +++ b/src/Bladeburner/ui/TravelPopup.tsx @@ -1,9 +1,10 @@ import React from "react"; import { removePopup } from "../../ui/React/createPopup"; import { BladeburnerConstants } from "../data/Constants"; +import { IBladeburner } from "../IBladeburner"; interface IProps { - bladeburner: any; + bladeburner: IBladeburner; popupId: string; } diff --git a/src/engine.jsx b/src/engine.jsx index db76bee30..dd2f676b7 100644 --- a/src/engine.jsx +++ b/src/engine.jsx @@ -36,6 +36,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 +230,7 @@ const Engine = { infiltrationContent: null, stockMarketContent: null, gangContent: null, + bladeburnerContent: null, locationContent: null, workInProgressContent: null, redPillContent: null, @@ -470,16 +472,15 @@ const Engine = { }, loadBladeburnerContent: function() { - if (Player.bladeburner instanceof Bladeburner) { - try { - Engine.hideAllContent(); - routing.navigateTo(Page.Bladeburner); - Player.bladeburner.createContent(); - MainMenuLinks.Bladeburner.classList.add("active"); - } catch(e) { - exceptionAlert(e); - } - } + if (!(Player.bladeburner instanceof Bladeburner)) return; + Engine.hideAllContent(); + routing.navigateTo(Page.Bladeburner); + Engine.Display.bladeburnerContent.style.display = "block"; + ReactDOM.render( + , + Engine.Display.bladeburnerContent, + ); + MainMenuLinks.Bladeburner.classList.add("active"); }, loadSleevesContent: function() { @@ -535,6 +536,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"; @@ -548,10 +552,6 @@ const Engine = { Player.corporation.clearUI(); } - if (Player.bladeburner instanceof Bladeburner) { - Player.bladeburner.clearContent(); - } - clearResleevesPage(); clearSleevesPage(); @@ -1260,6 +1260,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"; diff --git a/src/index.html b/src/index.html index aa5066561..fb9c707f9 100644 --- a/src/index.html +++ b/src/index.html @@ -232,8 +232,10 @@ if (htmlWebpackPlugin.options.googleAnalytics.trackingId) { %>
        -
        -
        +
        + + +
        From 58ada6d128950d99eedaa4cf5260751be902963c Mon Sep 17 00:00:00 2001 From: Olivier Gagnon Date: Mon, 16 Aug 2021 00:11:52 -0400 Subject: [PATCH 06/15] converting the giant Bladeburner object. --- src/Bladeburner.jsx | 773 ++-------------------------- src/Bladeburner/ActionIdentifier.ts | 7 +- src/Bladeburner/Bladeburner.ts | 744 ++++++++++++++++++++++++++ src/Bladeburner/IBladeburner.ts | 9 +- src/Bladeburner/data/Help.ts | 1 + src/Bladeburner/ui/Console.tsx | 2 +- src/Bladeburner/ui/Stats.tsx | 2 +- src/PersonObjects/IPlayer.ts | 1 + 8 files changed, 802 insertions(+), 737 deletions(-) create mode 100644 src/Bladeburner/Bladeburner.ts diff --git a/src/Bladeburner.jsx b/src/Bladeburner.jsx index 72449b356..6b21e5f7f 100644 --- a/src/Bladeburner.jsx +++ b/src/Bladeburner.jsx @@ -25,7 +25,6 @@ import { } from "../utils/StringHelperFunctions"; import { Settings } from "./Settings/Settings"; -import { ConsoleHelpText } from "./Bladeburner/data/Help"; import { City } from "./Bladeburner/City"; import { BladeburnerConstants } from "./Bladeburner/data/Constants"; import { Skill } from "./Bladeburner/Skill"; @@ -64,6 +63,25 @@ import { Money } from "./ui/React/Money"; import React from "react"; import ReactDOM from "react-dom"; +import { + getActionIdFromTypeAndName, + executeStartConsoleCommand, + executeSkillConsoleCommand, + executeLogConsoleCommand, + executeHelpConsoleCommand, + executeAutomateConsoleCommand, + parseCommandArguments, + executeConsoleCommand, + executeConsoleCommands, + triggerMigration, + triggerPotentialMigration, + randomEvent, + gainActionStats, + getDiplomacyEffectiveness, + getRecruitmentSuccessChance, + getRecruitmentTime, +} from "./Bladeburner/Bladeburner"; + function Bladeburner(params={}) { this.numHosp = 0; // Number of hospitalizations this.moneyLost = 0; // Money lost due to hospitalizations @@ -329,7 +347,7 @@ Bladeburner.prototype.process = function() { // Random Events this.randomEventCounter -= seconds; if (this.randomEventCounter <= 0) { - this.randomEvent(); + randomEvent(this); // Add instead of setting because we might have gone over the required time for the event this.randomEventCounter += getRandomInt(240, 600); } @@ -564,7 +582,7 @@ Bladeburner.prototype.startAction = function(actionId) { } break; case ActionTypes["Recruitment"]: - this.actionTimeToComplete = this.getRecruitmentTime(); + this.actionTimeToComplete = getRecruitmentTime(this, Player); break; case ActionTypes["Training"]: case ActionTypes["FieldAnalysis"]: @@ -620,7 +638,7 @@ Bladeburner.prototype.completeAction = function() { // Process Contract/Operation success/failure if (action.attempt(this)) { - this.gainActionStats(action, true); + gainActionStats(this, Player, action, true); ++action.successes; --action.count; @@ -648,7 +666,7 @@ Bladeburner.prototype.completeAction = function() { } isOperation ? this.completeOperation(true) : this.completeContract(true); } else { - this.gainActionStats(action, false); + gainActionStats(this, Player, action, false); ++action.failures; var loss = 0, damage = 0; if (action.rankLoss) { @@ -699,7 +717,7 @@ Bladeburner.prototype.completeAction = function() { var teamCount = action.teamCount, teamLossMax; if (action.attempt(this)) { - this.gainActionStats(action, true); + gainActionStats(this, Player, action, true); action.count = 0; this.blackops[action.name] = true; var rankGain = 0; @@ -719,7 +737,7 @@ Bladeburner.prototype.completeAction = function() { this.log(action.name + " successful! Gained " + formatNumber(rankGain, 1) + " rank"); } } else { - this.gainActionStats(action, false); + gainActionStats(this, Player, action, false); var rankLoss = 0, damage = 0; if (action.rankLoss) { rankLoss = addOffset(action.rankLoss, 10); @@ -801,7 +819,7 @@ Bladeburner.prototype.completeAction = function() { this.startAction(this.action); // Repeat action break; case ActionTypes["Recruitment"]: - var successChance = this.getRecruitmentSuccessChance(); + var successChance = getRecruitmentSuccessChance(this, Player); if (Math.random() < successChance) { var expGain = 2 * BladeburnerConstants.BaseStatGain * this.actionTimeToComplete; Player.gainCharismaExp(expGain); @@ -819,7 +837,7 @@ Bladeburner.prototype.completeAction = function() { this.startAction(this.action); // Repeat action break; case ActionTypes["Diplomacy"]: - var eff = this.getDiplomacyEffectiveness(); + var eff = getDiplomacyEffectiveness(this, Player); this.getCurrentCity().chaos *= eff; if (this.getCurrentCity().chaos < 0) { this.getCurrentCity().chaos = 0; } if (this.logging.general) { @@ -903,7 +921,7 @@ Bladeburner.prototype.completeOperation = function(success) { city.improveCommunityEstimate(1); } } else { - this.triggerPotentialMigration(this.city, 0.1); + triggerPotentialMigration(this, this.city, 0.1); } break; case "Undercover Operation": @@ -913,7 +931,7 @@ Bladeburner.prototype.completeOperation = function(success) { city.improveCommunityEstimate(1); } } else { - this.triggerPotentialMigration(this.city, 0.15); + triggerPotentialMigration(this, this.city, 0.15); } break; case "Sting Operation": @@ -950,174 +968,10 @@ Bladeburner.prototype.completeOperation = function(success) { } } -Bladeburner.prototype.getRecruitmentTime = function() { - var effCharisma = Player.charisma * this.skillMultipliers.effCha; - var charismaFactor = Math.pow(effCharisma, 0.81) + effCharisma / 90; - return Math.max(10, Math.round(BladeburnerConstants.BaseRecruitmentTimeNeeded - charismaFactor)); -} -Bladeburner.prototype.getRecruitmentSuccessChance = function() { - return Math.pow(Player.charisma, 0.45) / (this.teamSize + 1); -} - -Bladeburner.prototype.getDiplomacyEffectiveness = function() { - // Returns a decimal by which the city's chaos level should be multiplied (e.g. 0.98) - const CharismaLinearFactor = 1e3; - const CharismaExponentialFactor = 0.045; - - const charismaEff = Math.pow(Player.charisma, CharismaExponentialFactor) + Player.charisma / CharismaLinearFactor; - return (100 - charismaEff) / 100; -} - -/** - * Process stat gains from Contracts, Operations, and Black Operations - * @param action(Action obj) - Derived action class - * @param success(bool) - Whether action was successful - */ -Bladeburner.prototype.gainActionStats = function(action, success) { - var difficulty = action.getDifficulty(); - - /** - * Gain multiplier based on difficulty. If this changes then the - * same variable calculated in completeAction() needs to change too - */ - var difficultyMult = Math.pow(difficulty, BladeburnerConstants.DiffMultExponentialFactor) + difficulty / BladeburnerConstants.DiffMultLinearFactor; - - var time = this.actionTimeToComplete; - var successMult = success ? 1 : 0.5; - - var unweightedGain = time * BladeburnerConstants.BaseStatGain * successMult * difficultyMult; - var unweightedIntGain = time * BladeburnerConstants.BaseIntGain * successMult * difficultyMult; - const skillMult = this.skillMultipliers.expGain; - Player.gainHackingExp(unweightedGain * action.weights.hack * Player.hacking_exp_mult * skillMult); - Player.gainStrengthExp(unweightedGain * action.weights.str * Player.strength_exp_mult * skillMult); - Player.gainDefenseExp(unweightedGain * action.weights.def * Player.defense_exp_mult * skillMult); - Player.gainDexterityExp(unweightedGain * action.weights.dex * Player.dexterity_exp_mult * skillMult); - Player.gainAgilityExp(unweightedGain * action.weights.agi * Player.agility_exp_mult * skillMult); - Player.gainCharismaExp(unweightedGain * action.weights.cha * Player.charisma_exp_mult * skillMult); - let intExp = unweightedIntGain * action.weights.int * skillMult; - if (intExp > 1) { - intExp = Math.pow(intExp, 0.8); - } - Player.gainIntelligenceExp(intExp); -} - -Bladeburner.prototype.randomEvent = function() { - var chance = Math.random(); - - // Choose random source/destination city for events - var sourceCityName = BladeburnerConstants.CityNames[getRandomInt(0, 5)]; - var sourceCity = this.cities[sourceCityName]; - if (!(sourceCity instanceof City)) { - throw new Error("sourceCity was not a City object in Bladeburner.randomEvent()"); - } - - var destCityName = BladeburnerConstants.CityNames[getRandomInt(0, 5)]; - while (destCityName === sourceCityName) { - destCityName = BladeburnerConstants.CityNames[getRandomInt(0, 5)]; - } - var destCity = this.cities[destCityName]; - - if (!(sourceCity instanceof City) || !(destCity instanceof City)) { - throw new Error("sourceCity/destCity was not a City object in Bladeburner.randomEvent()"); - } - - if (chance <= 0.05) { - // New Synthoid Community, 5% - ++sourceCity.comms; - var percentage = getRandomInt(10, 20) / 100; - var count = Math.round(sourceCity.pop * percentage); - sourceCity.pop += count; - if (this.logging.events) { - this.log("Intelligence indicates that a new Synthoid community was formed in a city"); - } - } else if (chance <= 0.1) { - // Synthoid Community Migration, 5% - if (sourceCity.comms <= 0) { - // If no comms in source city, then instead trigger a new Synthoid community event - ++sourceCity.comms; - var percentage = getRandomInt(10, 20) / 100; - var count = Math.round(sourceCity.pop * percentage); - sourceCity.pop += count; - if (this.logging.events) { - this.log("Intelligence indicates that a new Synthoid community was formed in a city"); - } - } else { - --sourceCity.comms; - ++destCity.comms; - - // Change pop - var percentage = getRandomInt(10, 20) / 100; - var count = Math.round(sourceCity.pop * percentage); - sourceCity.pop -= count; - destCity.pop += count; - - if (this.logging.events) { - this.log("Intelligence indicates that a Synthoid community migrated from " + sourceCityName + " to some other city"); - } - } - } else if (chance <= 0.3) { - // New Synthoids (non community), 20% - var percentage = getRandomInt(8, 24) / 100; - var count = Math.round(sourceCity.pop * percentage); - sourceCity.pop += count; - if (this.logging.events) { - this.log("Intelligence indicates that the Synthoid population of " + sourceCityName + " just changed significantly"); - } - } else if (chance <= 0.5) { - // Synthoid migration (non community) 20% - this.triggerMigration(sourceCityName); - if (this.logging.events) { - this.log("Intelligence indicates that a large number of Synthoids migrated from " + sourceCityName + " to some other city"); - } - } else if (chance <= 0.7) { - // Synthoid Riots (+chaos), 20% - sourceCity.chaos += 1; - sourceCity.chaos *= (1 + getRandomInt(5, 20) / 100); - if (this.logging.events) { - this.log("Tensions between Synthoids and humans lead to riots in " + sourceCityName + "! Chaos increased"); - } - } else if (chance <= 0.9) { - // Less Synthoids, 20% - var percentage = getRandomInt(8, 20) / 100; - var count = Math.round(sourceCity.pop * percentage); - sourceCity.pop -= count; - if (this.logging.events) { - this.log("Intelligence indicates that the Synthoid population of " + sourceCityName + " just changed significantly"); - } - } - // 10% chance of nothing happening -} - -Bladeburner.prototype.triggerPotentialMigration = function(sourceCityName, chance) { - if (chance == null || isNaN(chance)) { - console.error("Invalid 'chance' parameter passed into Bladeburner.triggerPotentialMigration()"); - } - if (chance > 1) {chance /= 100;} - if (Math.random() < chance) {this.triggerMigration(sourceCityName);} -} - -Bladeburner.prototype.triggerMigration = function(sourceCityName) { - var destCityName = BladeburnerConstants.CityNames[getRandomInt(0, 5)]; - while (destCityName === sourceCityName) { - destCityName = BladeburnerConstants.CityNames[getRandomInt(0, 5)]; - } - var destCity = this.cities[destCityName]; - var sourceCity = this.cities[sourceCityName]; - if (destCity == null || sourceCity == null) { - throw new Error("Failed to find City with name: " + destCityName); - } - var rand = Math.random(), percentage = getRandomInt(3, 15) / 100; - - if (rand < 0.05 && sourceCity.comms > 0) { // 5% chance for community migration - percentage *= getRandomInt(2, 4); // Migration increases population change - --sourceCity.comms; - ++destCity.comms; - } - var count = Math.round(sourceCity.pop * percentage); - sourceCity.pop -= count; - destCity.pop += count; -} +//////////////////////////////////////////////////////////////////////////////// +/////////////////////////////Unconvertable for now////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// // Bladeburner Console Window Bladeburner.prototype.postToConsole = function(input, saveToLogs=true) { @@ -1130,10 +984,6 @@ Bladeburner.prototype.postToConsole = function(input, saveToLogs=true) { } } -Bladeburner.prototype.clearConsole = function() { - this.consoleLogs.length = 0; -} - Bladeburner.prototype.log = function(input) { // Adds a timestamp and then just calls postToConsole this.postToConsole(`[${getTimestamp()}] ${input}`); @@ -1141,547 +991,12 @@ Bladeburner.prototype.log = function(input) { // Handles a potential series of commands (comm1; comm2; comm3;) Bladeburner.prototype.executeConsoleCommands = function(commands) { - try { - // Console History - if (this.consoleHistory[this.consoleHistory.length-1] != commands) { - this.consoleHistory.push(commands); - if (this.consoleHistory.length > 50) { - this.consoleHistory.splice(0, 1); - } - } - - const arrayOfCommands = commands.split(";"); - for (let i = 0; i < arrayOfCommands.length; ++i) { - this.executeConsoleCommand(arrayOfCommands[i]); - } - } catch(e) { - exceptionAlert(e); - } + executeConsoleCommands(this, commands); } -// Execute a single console command -Bladeburner.prototype.executeConsoleCommand = function(command) { - command = command.trim(); - command = command.replace(/\s\s+/g, ' '); // Replace all whitespace w/ a single space - - var args = this.parseCommandArguments(command); - if (args.length <= 0) {return;} // Log an error? - - switch(args[0].toLowerCase()) { - case "automate": - this.executeAutomateConsoleCommand(args); - break; - case "clear": - case "cls": - this.clearConsole(); - break; - case "help": - this.executeHelpConsoleCommand(args); - break; - case "log": - this.executeLogConsoleCommand(args); - break; - case "skill": - this.executeSkillConsoleCommand(args); - break; - case "start": - this.executeStartConsoleCommand(args); - break; - case "stop": - this.resetAction(); - break; - default: - this.postToConsole("Invalid console command"); - break; - } -} - -Bladeburner.prototype.parseCommandArguments = function(command) { - /** - * Returns an array with command and its arguments in each index. - * e.g. skill "blade's intuition" foo returns [skill, blade's intuition, foo] - * The input to this fn will be trimmed and will have all whitespace replaced w/ a single space - */ - const args = []; - let start = 0, i = 0; - while (i < command.length) { - const c = command.charAt(i); - if (c === '"') { // Double quotes - const endQuote = command.indexOf('"', i+1); - if (endQuote !== -1 && (endQuote === command.length-1 || command.charAt(endQuote+1) === " ")) { - args.push(command.substr(i+1, (endQuote - i - 1))); - if (endQuote === command.length-1) { - start = i = endQuote+1; - } else { - start = i = endQuote+2; // Skip the space - } - continue; - } - } else if (c === "'") { // Single quotes, same thing as above - const endQuote = command.indexOf("'", i+1); - if (endQuote !== -1 && (endQuote === command.length-1 || command.charAt(endQuote+1) === " ")) { - args.push(command.substr(i+1, (endQuote - i - 1))); - if (endQuote === command.length-1) { - start = i = endQuote+1; - } else { - start = i = endQuote+2; // Skip the space - } - continue; - } - } else if (c === " ") { - args.push(command.substr(start, i-start)); - start = i+1; - } - ++i; - } - if (start !== i) {args.push(command.substr(start, i-start));} - return args; -} - -Bladeburner.prototype.executeAutomateConsoleCommand = function(args) { - if (args.length !== 2 && args.length !== 4) { - this.postToConsole("Invalid use of 'automate' command: automate [var] [val] [hi/low]. Use 'help automate' for more info"); - return; - } - - // Enable/Disable - if (args.length === 2) { - var flag = args[1]; - if (flag.toLowerCase() === "status") { - this.postToConsole("Automation: " + (this.automateEnabled ? "enabled" : "disabled")); - if (this.automateEnabled) { - this.postToConsole("When your stamina drops to " + formatNumber(this.automateThreshLow, 0) + - ", you will automatically switch to " + this.automateActionLow.name + - ". When your stamina recovers to " + - formatNumber(this.automateThreshHigh, 0) + ", you will automatically " + - "switch to " + this.automateActionHigh.name + "."); - } - - } else if (flag.toLowerCase().includes("en")) { - if (!(this.automateActionLow instanceof ActionIdentifier) || - !(this.automateActionHigh instanceof ActionIdentifier)) { - return this.log("Failed to enable automation. Actions were not set"); - } - this.automateEnabled = true; - this.log("Bladeburner automation enabled"); - } else if (flag.toLowerCase().includes("d")) { - this.automateEnabled = false; - this.log("Bladeburner automation disabled"); - } else { - this.log("Invalid argument for 'automate' console command: " + args[1]); - } - return; - } - - // Set variables - if (args.length === 4) { - var variable = args[1], val = args[2]; - - var highLow = false; // True for high, false for low - if (args[3].toLowerCase().includes("hi")) {highLow = true;} - - switch (variable) { - case "general": - case "gen": - if (GeneralActions[val] != null) { - var action = new ActionIdentifier({ - type:ActionTypes[val], name:val, - }); - if (highLow) { - this.automateActionHigh = action; - } else { - this.automateActionLow = action; - } - this.log("Automate (" + (highLow ? "HIGH" : "LOW") + ") action set to " + val); - } else { - this.postToConsole("Invalid action name specified: " + val); - } - break; - case "contract": - case "contracts": - if (this.contracts[val] != null) { - var action = new ActionIdentifier({ - type:ActionTypes.Contract, name:val, - }); - if (highLow) { - this.automateActionHigh = action; - } else { - this.automateActionLow = action; - } - this.log("Automate (" + (highLow ? "HIGH" : "LOW") + ") action set to " + val); - } else { - this.postToConsole("Invalid contract name specified: " + val); - } - break; - case "ops": - case "op": - case "operations": - case "operation": - if (this.operations[val] != null) { - var action = new ActionIdentifier({ - type:ActionTypes.Operation, name:val, - }); - if (highLow) { - this.automateActionHigh = action; - } else { - this.automateActionLow = action; - } - this.log("Automate (" + (highLow ? "HIGH" : "LOW") + ") action set to " + val); - } else { - this.postToConsole("Invalid Operation name specified: " + val); - } - break; - case "stamina": - if (isNaN(val)) { - this.postToConsole("Invalid value specified for stamina threshold (must be numeric): " + val); - } else { - if (highLow) { - this.automateThreshHigh = Number(val); - } else { - this.automateThreshLow = Number(val); - } - this.log("Automate (" + (highLow ? "HIGH" : "LOW") + ") stamina threshold set to " + val); - } - break; - default: - break; - } - - return; - } -} - -Bladeburner.prototype.executeHelpConsoleCommand = function(args) { - if (args.length === 1) { - for(const line of ConsoleHelpText.helpList){ - this.postToConsole(line); - } - } else { - for (var i = 1; i < args.length; ++i) { - const helpText = ConsoleHelpText[args[i]]; - for(const line of helpText){ - this.postToConsole(line); - } - } - } -} - -Bladeburner.prototype.executeLogConsoleCommand = function(args) { - if (args.length < 3) { - this.postToConsole("Invalid usage of log command: log [enable/disable] [action/event]"); - this.postToConsole("Use 'help log' for more details and examples"); - return; - } - - var flag = true; - if (args[1].toLowerCase().includes("d")) {flag = false;} // d for disable - - switch (args[2].toLowerCase()) { - case "general": - case "gen": - this.logging.general = flag; - this.log("Logging " + (flag ? "enabled" : "disabled") + " for general actions"); - break; - case "contract": - case "contracts": - this.logging.contracts = flag; - this.log("Logging " + (flag ? "enabled" : "disabled") + " for Contracts"); - break; - case "ops": - case "op": - case "operations": - case "operation": - this.logging.ops = flag; - this.log("Logging " + (flag ? "enabled" : "disabled") + " for Operations"); - break; - case "blackops": - case "blackop": - case "black operations": - case "black operation": - this.logging.blackops = flag; - this.log("Logging " + (flag ? "enabled" : "disabled") + " for BlackOps"); - break; - case "event": - case "events": - this.logging.events = flag; - this.log("Logging " + (flag ? "enabled" : "disabled") + " for events"); - break; - case "all": - this.logging.general = flag; - this.logging.contracts = flag; - this.logging.ops = flag; - this.logging.blackops = flag; - this.logging.events = flag; - this.log("Logging " + (flag ? "enabled" : "disabled") + " for everything"); - break; - default: - this.postToConsole("Invalid action/event type specified: " + args[2]); - this.postToConsole("Examples of valid action/event identifiers are: [general, contracts, ops, blackops, events]"); - break; - } -} - -Bladeburner.prototype.executeSkillConsoleCommand = function(args) { - switch (args.length) { - case 1: - // Display Skill Help Command - this.postToConsole("Invalid usage of 'skill' console command: skill [action] [name]"); - this.postToConsole("Use 'help skill' for more info"); - break; - case 2: - if (args[1].toLowerCase() === "list") { - // List all skills and their level - this.postToConsole("Skills: "); - var skillNames = Object.keys(Skills); - for(var i = 0; i < skillNames.length; ++i) { - var skill = Skills[skillNames[i]]; - var level = 0; - if (this.skills[skill.name] != null) {level = this.skills[skill.name];} - this.postToConsole(skill.name + ": Level " + formatNumber(level, 0)); - } - this.postToConsole(" "); - this.postToConsole("Effects: "); - 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]) { - case "successChanceAll": - this.postToConsole("Total Success Chance: x" + mult); - break; - case "successChanceStealth": - this.postToConsole("Stealth Success Chance: x" + mult); - break; - case "successChanceKill": - this.postToConsole("Retirement Success Chance: x" + mult); - break; - case "successChanceContract": - this.postToConsole("Contract Success Chance: x" + mult); - break; - case "successChanceOperation": - this.postToConsole("Operation Success Chance: x" + mult); - break; - case "successChanceEstimate": - this.postToConsole("Synthoid Data Estimate: x" + mult); - break; - case "actionTime": - this.postToConsole("Action Time: x" + mult); - break; - case "effHack": - this.postToConsole("Hacking Skill: x" + mult); - break; - case "effStr": - this.postToConsole("Strength: x" + mult); - break; - case "effDef": - this.postToConsole("Defense: x" + mult); - break; - case "effDex": - this.postToConsole("Dexterity: x" + mult); - break; - case "effAgi": - this.postToConsole("Agility: x" + mult); - break; - case "effCha": - this.postToConsole("Charisma: x" + mult); - break; - case "effInt": - this.postToConsole("Intelligence: x" + mult); - break; - case "stamina": - this.postToConsole("Stamina: x" + mult); - break; - default: - console.warn(`Unrecognized SkillMult Key: ${multKeys[i]}`); - break; - } - } - } - } else { - this.postToConsole("Invalid usage of 'skill' console command: skill [action] [name]"); - this.postToConsole("Use 'help skill' for more info"); - } - break; - case 3: - var skillName = args[2]; - var skill = Skills[skillName]; - if (skill == null || !(skill instanceof Skill)) { - return this.postToConsole("Invalid skill name (Note that this is case-sensitive): " + skillName); - } - if (args[1].toLowerCase() === "list") { - let level = 0; - if (this.skills[skill.name] !== undefined) { - level = this.skills[skill.name]; - } - this.postToConsole(skill.name + ": Level " + formatNumber(level), 0); - } else if (args[1].toLowerCase() === "level") { - var currentLevel = 0; - if (this.skills[skillName] && !isNaN(this.skills[skillName])) { - currentLevel = this.skills[skillName]; - } - var pointCost = skill.calculateCost(currentLevel); - if (this.skillPoints >= pointCost) { - this.skillPoints -= pointCost; - this.upgradeSkill(skill); - this.log(skill.name + " upgraded to Level " + this.skills[skillName]); - } else { - this.postToConsole("You do not have enough Skill Points to upgrade this. You need " + formatNumber(pointCost, 0)); - } - - } else { - this.postToConsole("Invalid usage of 'skill' console command: skill [action] [name]"); - this.postToConsole("Use 'help skill' for more info"); - } - break; - default: - this.postToConsole("Invalid usage of 'skill' console command: skill [action] [name]"); - this.postToConsole("Use 'help skill' for more info"); - break; - } -} - -Bladeburner.prototype.executeStartConsoleCommand = function(args) { - if (args.length !== 3) { - this.postToConsole("Invalid usage of 'start' console command: start [type] [name]"); - this.postToConsole("Use 'help start' for more info"); - return; - } - var name = args[2]; - switch (args[1].toLowerCase()) { - case "general": - case "gen": - if (GeneralActions[name] != null) { - this.action.type = ActionTypes[name]; - this.action.name = name; - this.startAction(this.action); - } else { - this.postToConsole("Invalid action name specified: " + args[2]); - } - break; - case "contract": - case "contracts": - if (this.contracts[name] != null) { - this.action.type = ActionTypes.Contract; - this.action.name = name; - this.startAction(this.action); - } else { - this.postToConsole("Invalid contract name specified: " + args[2]); - } - break; - case "ops": - case "op": - case "operations": - case "operation": - if (this.operations[name] != null) { - this.action.type = ActionTypes.Operation; - this.action.name = name; - this.startAction(this.action); - } else { - this.postToConsole("Invalid Operation name specified: " + args[2]); - } - break; - case "blackops": - case "blackop": - case "black operations": - case "black operation": - if (BlackOperations[name] != null) { - this.action.type = ActionTypes.BlackOperation; - this.action.name = name; - this.startAction(this.action); - } else { - this.postToConsole("Invalid BlackOp name specified: " + args[2]); - } - break; - default: - this.postToConsole("Invalid action/event type specified: " + args[1]); - this.postToConsole("Examples of valid action/event identifiers are: [general, contract, op, blackop]"); - break; - } -} - -Bladeburner.prototype.getActionIdFromTypeAndName = function(type="", name="") { - if (type === "" || name === "") {return null;} - var action = new ActionIdentifier(); - var convertedType = type.toLowerCase().trim(); - var convertedName = name.toLowerCase().trim(); - switch (convertedType) { - case "contract": - case "contracts": - case "contr": - action.type = ActionTypes["Contract"]; - if (this.contracts.hasOwnProperty(name)) { - action.name = name; - return action; - } else { - return null; - } - break; - case "operation": - case "operations": - case "op": - case "ops": - action.type = ActionTypes["Operation"]; - if (this.operations.hasOwnProperty(name)) { - action.name = name; - return action; - } else { - return null; - } - break; - case "blackoperation": - case "black operation": - case "black operations": - case "black op": - case "black ops": - case "blackop": - case "blackops": - action.type = ActionTypes["BlackOp"]; - if (BlackOperations.hasOwnProperty(name)) { - action.name = name; - return action; - } else { - return null; - } - break; - case "general": - case "general action": - case "gen": - break; - default: - return null; - } - - if (convertedType.startsWith("gen")) { - switch (convertedName) { - case "training": - action.type = ActionTypes["Training"]; - action.name = "Training"; - break; - case "recruitment": - case "recruit": - action.type = ActionTypes["Recruitment"]; - action.name = "Recruitment"; - break; - case "field analysis": - case "fieldanalysis": - action.type = ActionTypes["Field Analysis"]; - action.name = "Field Analysis"; - break; - case "diplomacy": - action.type = ActionTypes["Diplomacy"]; - action.name = "Diplomacy"; - break; - case "hyperbolic regeneration chamber": - action.type = ActionTypes["Hyperbolic Regeneration Chamber"]; - action.name = "Hyperbolic Regeneration Chamber"; - break; - default: - return null; - } - return action; - } -} +//////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////Netscript Fns////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// Bladeburner.prototype.getTypeAndNameFromActionId = function(actionId) { var res = {}; @@ -1720,7 +1035,7 @@ Bladeburner.prototype.getSkillNamesNetscriptFn = function() { Bladeburner.prototype.startActionNetscriptFn = function(type, name, workerScript) { const errorLogText = `Invalid action: type='${type}' name='${name}'`; - const actionId = this.getActionIdFromTypeAndName(type, name); + const actionId = getActionIdFromTypeAndName(this, type, name); if (actionId == null) { workerScript.log("bladeburner.startAction", errorLogText); return false; @@ -1777,7 +1092,7 @@ Bladeburner.prototype.startActionNetscriptFn = function(type, name, workerScript Bladeburner.prototype.getActionTimeNetscriptFn = function(type, name, workerScript) { const errorLogText = `Invalid action: type='${type}' name='${name}'` - const actionId = this.getActionIdFromTypeAndName(type, name); + const actionId = getActionIdFromTypeAndName(this, type, name); if (actionId == null) { workerScript.log("bladeburner.getActionTime", errorLogText); return -1; @@ -1800,7 +1115,7 @@ Bladeburner.prototype.getActionTimeNetscriptFn = function(type, name, workerScri case ActionTypes["FieldAnalysis"]: return 30; case ActionTypes["Recruitment"]: - return this.getRecruitmentTime(); + return getRecruitmentTime(this, Player); case ActionTypes["Diplomacy"]: case ActionTypes["Hyperbolic Regeneration Chamber"]: return 60; @@ -1812,7 +1127,7 @@ Bladeburner.prototype.getActionTimeNetscriptFn = function(type, name, workerScri Bladeburner.prototype.getActionEstimatedSuccessChanceNetscriptFn = function(type, name, workerScript) { const errorLogText = `Invalid action: type='${type}' name='${name}'` - const actionId = this.getActionIdFromTypeAndName(type, name); + const actionId = getActionIdFromTypeAndName(this, type, name); if (actionId == null) { workerScript.log("bladeburner.getActionEstimatedSuccessChance", errorLogText); return -1; @@ -1835,7 +1150,7 @@ Bladeburner.prototype.getActionEstimatedSuccessChanceNetscriptFn = function(type case ActionTypes["FieldAnalysis"]: return 1; case ActionTypes["Recruitment"]: - return this.getRecruitmentSuccessChance(); + return getRecruitmentSuccessChance(this, Player); default: workerScript.log("bladeburner.getActionEstimatedSuccessChance", errorLogText); return -1; @@ -1844,7 +1159,7 @@ Bladeburner.prototype.getActionEstimatedSuccessChanceNetscriptFn = function(type Bladeburner.prototype.getActionCountRemainingNetscriptFn = function(type, name, workerScript) { const errorLogText = `Invalid action: type='${type}' name='${name}'`; - const actionId = this.getActionIdFromTypeAndName(type, name); + const actionId = getActionIdFromTypeAndName(this, type, name); if (actionId == null) { workerScript.log("bladeburner.getActionCountRemaining", errorLogText); return -1; @@ -1940,7 +1255,7 @@ Bladeburner.prototype.getTeamSizeNetscriptFn = function(type, name, workerScript } const errorLogText = `Invalid action: type='${type}' name='${name}'`; - const actionId = this.getActionIdFromTypeAndName(type, name); + const actionId = getActionIdFromTypeAndName(this, type, name); if (actionId == null) { workerScript.log("bladeburner.getTeamSize", errorLogText); return -1; @@ -1963,7 +1278,7 @@ Bladeburner.prototype.getTeamSizeNetscriptFn = function(type, name, workerScript Bladeburner.prototype.setTeamSizeNetscriptFn = function(type, name, size, workerScript) { const errorLogText = `Invalid action: type='${type}' name='${name}'`; - const actionId = this.getActionIdFromTypeAndName(type, name); + const actionId = getActionIdFromTypeAndName(this, type, name); if (actionId == null) { workerScript.log("bladeburner.setTeamSize", errorLogText); return -1; diff --git a/src/Bladeburner/ActionIdentifier.ts b/src/Bladeburner/ActionIdentifier.ts index 4358f3ba1..4d520d833 100644 --- a/src/Bladeburner/ActionIdentifier.ts +++ b/src/Bladeburner/ActionIdentifier.ts @@ -1,3 +1,4 @@ +import { IActionIdentifier } from "./IActionIdentifier"; import { Generic_fromJSON, Generic_toJSON, Reviver } from "../../utils/JSONReviver"; interface IParams { @@ -5,9 +6,9 @@ interface IParams { type?: number; } -export class ActionIdentifier { - name?: string; - type?: number; +export class ActionIdentifier implements IActionIdentifier { + name: string = ""; + type: number = -1; constructor(params: IParams = {}) { if (params.name) this.name = params.name; diff --git a/src/Bladeburner/Bladeburner.ts b/src/Bladeburner/Bladeburner.ts new file mode 100644 index 000000000..a121ed51a --- /dev/null +++ b/src/Bladeburner/Bladeburner.ts @@ -0,0 +1,744 @@ +/* + Here we have a bunch of functions converted to typescript, eventually they + will go back into a Bladeburner class. +*/ +import { IBladeburner } from "./IBladeburner"; +import { IActionIdentifier } from "./IActionIdentifier"; +import { ActionIdentifier } from "./ActionIdentifier"; +import { ActionTypes } from "./data/ActionTypes"; +import { BlackOperations } from "./BlackOperations"; +import { GeneralActions } from "./GeneralActions"; +import { formatNumber } from "../../utils/StringHelperFunctions"; +import { Skills } from "./Skills"; +import { Skill } from "./Skill"; +import { City } from "./City"; +import { IAction } from "./IAction"; +import { IPlayer } from "../PersonObjects/IPlayer"; +import { ConsoleHelpText } from "./data/Help"; +import { exceptionAlert } from "../../utils/helpers/exceptionAlert"; +import { getRandomInt } from "../../utils/helpers/getRandomInt"; +import { BladeburnerConstants } from "./data/Constants"; + +export function getActionIdFromTypeAndName(bladeburner: IBladeburner, type: string = "", name: string = ""): IActionIdentifier | null { + if (type === "" || name === "") {return null;} + const action = new ActionIdentifier(); + const convertedType = type.toLowerCase().trim(); + const convertedName = name.toLowerCase().trim(); + switch (convertedType) { + case "contract": + case "contracts": + case "contr": + action.type = ActionTypes["Contract"]; + if (bladeburner.contracts.hasOwnProperty(name)) { + action.name = name; + return action; + } else { + return null; + } + break; + case "operation": + case "operations": + case "op": + case "ops": + action.type = ActionTypes["Operation"]; + if (bladeburner.operations.hasOwnProperty(name)) { + action.name = name; + return action; + } else { + return null; + } + break; + case "blackoperation": + case "black operation": + case "black operations": + case "black op": + case "black ops": + case "blackop": + case "blackops": + action.type = ActionTypes["BlackOp"]; + if (BlackOperations.hasOwnProperty(name)) { + action.name = name; + return action; + } else { + return null; + } + break; + case "general": + case "general action": + case "gen": + break; + default: + return null; + } + + if (convertedType.startsWith("gen")) { + switch (convertedName) { + case "training": + action.type = ActionTypes["Training"]; + action.name = "Training"; + break; + case "recruitment": + case "recruit": + action.type = ActionTypes["Recruitment"]; + action.name = "Recruitment"; + break; + case "field analysis": + case "fieldanalysis": + action.type = ActionTypes["Field Analysis"]; + action.name = "Field Analysis"; + break; + case "diplomacy": + action.type = ActionTypes["Diplomacy"]; + action.name = "Diplomacy"; + break; + case "hyperbolic regeneration chamber": + action.type = ActionTypes["Hyperbolic Regeneration Chamber"]; + action.name = "Hyperbolic Regeneration Chamber"; + break; + default: + return null; + } + return action; + } + + return null; +} + +export function executeStartConsoleCommand(bladeburner: IBladeburner, args: string[]): void { + if (args.length !== 3) { + bladeburner.postToConsole("Invalid usage of 'start' console command: start [type] [name]"); + bladeburner.postToConsole("Use 'help start' for more info"); + return; + } + const name = args[2]; + switch (args[1].toLowerCase()) { + case "general": + case "gen": + if (GeneralActions[name] != null) { + bladeburner.action.type = ActionTypes[name]; + bladeburner.action.name = name; + bladeburner.startAction(bladeburner.action); + } else { + bladeburner.postToConsole("Invalid action name specified: " + args[2]); + } + break; + case "contract": + case "contracts": + if (bladeburner.contracts[name] != null) { + bladeburner.action.type = ActionTypes.Contract; + bladeburner.action.name = name; + bladeburner.startAction(bladeburner.action); + } else { + bladeburner.postToConsole("Invalid contract name specified: " + args[2]); + } + break; + case "ops": + case "op": + case "operations": + case "operation": + if (bladeburner.operations[name] != null) { + bladeburner.action.type = ActionTypes.Operation; + bladeburner.action.name = name; + bladeburner.startAction(bladeburner.action); + } else { + bladeburner.postToConsole("Invalid Operation name specified: " + args[2]); + } + break; + case "blackops": + case "blackop": + case "black operations": + case "black operation": + if (BlackOperations[name] != null) { + bladeburner.action.type = ActionTypes.BlackOperation; + bladeburner.action.name = name; + bladeburner.startAction(bladeburner.action); + } else { + bladeburner.postToConsole("Invalid BlackOp name specified: " + args[2]); + } + break; + default: + bladeburner.postToConsole("Invalid action/event type specified: " + args[1]); + bladeburner.postToConsole("Examples of valid action/event identifiers are: [general, contract, op, blackop]"); + break; + } +} + +export function executeSkillConsoleCommand(bladeburner: IBladeburner, args: string[]): void { + switch (args.length) { + case 1: + // Display Skill Help Command + bladeburner.postToConsole("Invalid usage of 'skill' console command: skill [action] [name]"); + bladeburner.postToConsole("Use 'help skill' for more info"); + break; + case 2: + if (args[1].toLowerCase() === "list") { + // List all skills and their level + bladeburner.postToConsole("Skills: "); + const skillNames = Object.keys(Skills); + for(let i = 0; i < skillNames.length; ++i) { + let skill = Skills[skillNames[i]]; + let level = 0; + if (bladeburner.skills[skill.name] != null) {level = bladeburner.skills[skill.name];} + bladeburner.postToConsole(skill.name + ": Level " + formatNumber(level, 0)); + } + bladeburner.postToConsole(" "); + bladeburner.postToConsole("Effects: "); + const multKeys = Object.keys(bladeburner.skillMultipliers); + for (let i = 0; i < multKeys.length; ++i) { + let mult = bladeburner.skillMultipliers[multKeys[i]]; + if (mult && mult !== 1) { + mult = formatNumber(mult, 3); + switch(multKeys[i]) { + case "successChanceAll": + bladeburner.postToConsole("Total Success Chance: x" + mult); + break; + case "successChanceStealth": + bladeburner.postToConsole("Stealth Success Chance: x" + mult); + break; + case "successChanceKill": + bladeburner.postToConsole("Retirement Success Chance: x" + mult); + break; + case "successChanceContract": + bladeburner.postToConsole("Contract Success Chance: x" + mult); + break; + case "successChanceOperation": + bladeburner.postToConsole("Operation Success Chance: x" + mult); + break; + case "successChanceEstimate": + bladeburner.postToConsole("Synthoid Data Estimate: x" + mult); + break; + case "actionTime": + bladeburner.postToConsole("Action Time: x" + mult); + break; + case "effHack": + bladeburner.postToConsole("Hacking Skill: x" + mult); + break; + case "effStr": + bladeburner.postToConsole("Strength: x" + mult); + break; + case "effDef": + bladeburner.postToConsole("Defense: x" + mult); + break; + case "effDex": + bladeburner.postToConsole("Dexterity: x" + mult); + break; + case "effAgi": + bladeburner.postToConsole("Agility: x" + mult); + break; + case "effCha": + bladeburner.postToConsole("Charisma: x" + mult); + break; + case "effInt": + bladeburner.postToConsole("Intelligence: x" + mult); + break; + case "stamina": + bladeburner.postToConsole("Stamina: x" + mult); + break; + default: + console.warn(`Unrecognized SkillMult Key: ${multKeys[i]}`); + break; + } + } + } + } else { + bladeburner.postToConsole("Invalid usage of 'skill' console command: skill [action] [name]"); + bladeburner.postToConsole("Use 'help skill' for more info"); + } + break; + case 3: + const skillName = args[2]; + const skill = Skills[skillName]; + if (skill == null || !(skill instanceof Skill)) { + bladeburner.postToConsole("Invalid skill name (Note that it is case-sensitive): " + skillName); + } + if (args[1].toLowerCase() === "list") { + let level = 0; + if (bladeburner.skills[skill.name] !== undefined) { + level = bladeburner.skills[skill.name]; + } + bladeburner.postToConsole(skill.name + ": Level " + formatNumber(level)); + } else if (args[1].toLowerCase() === "level") { + let currentLevel = 0; + if (bladeburner.skills[skillName] && !isNaN(bladeburner.skills[skillName])) { + currentLevel = bladeburner.skills[skillName]; + } + const pointCost = skill.calculateCost(currentLevel); + if (bladeburner.skillPoints >= pointCost) { + bladeburner.skillPoints -= pointCost; + bladeburner.upgradeSkill(skill); + bladeburner.log(skill.name + " upgraded to Level " + bladeburner.skills[skillName]); + } else { + bladeburner.postToConsole("You do not have enough Skill Points to upgrade bladeburner. You need " + formatNumber(pointCost, 0)); + } + + } else { + bladeburner.postToConsole("Invalid usage of 'skill' console command: skill [action] [name]"); + bladeburner.postToConsole("Use 'help skill' for more info"); + } + break; + default: + bladeburner.postToConsole("Invalid usage of 'skill' console command: skill [action] [name]"); + bladeburner.postToConsole("Use 'help skill' for more info"); + break; + } +} + + +export function executeLogConsoleCommand(bladeburner: IBladeburner, args: string[]): void { + if (args.length < 3) { + bladeburner.postToConsole("Invalid usage of log command: log [enable/disable] [action/event]"); + bladeburner.postToConsole("Use 'help log' for more details and examples"); + return; + } + + let flag = true; + if (args[1].toLowerCase().includes("d")) {flag = false;} // d for disable + + switch (args[2].toLowerCase()) { + case "general": + case "gen": + bladeburner.logging.general = flag; + bladeburner.log("Logging " + (flag ? "enabled" : "disabled") + " for general actions"); + break; + case "contract": + case "contracts": + bladeburner.logging.contracts = flag; + bladeburner.log("Logging " + (flag ? "enabled" : "disabled") + " for Contracts"); + break; + case "ops": + case "op": + case "operations": + case "operation": + bladeburner.logging.ops = flag; + bladeburner.log("Logging " + (flag ? "enabled" : "disabled") + " for Operations"); + break; + case "blackops": + case "blackop": + case "black operations": + case "black operation": + bladeburner.logging.blackops = flag; + bladeburner.log("Logging " + (flag ? "enabled" : "disabled") + " for BlackOps"); + break; + case "event": + case "events": + bladeburner.logging.events = flag; + bladeburner.log("Logging " + (flag ? "enabled" : "disabled") + " for events"); + break; + case "all": + bladeburner.logging.general = flag; + bladeburner.logging.contracts = flag; + bladeburner.logging.ops = flag; + bladeburner.logging.blackops = flag; + bladeburner.logging.events = flag; + bladeburner.log("Logging " + (flag ? "enabled" : "disabled") + " for everything"); + break; + default: + bladeburner.postToConsole("Invalid action/event type specified: " + args[2]); + bladeburner.postToConsole("Examples of valid action/event identifiers are: [general, contracts, ops, blackops, events]"); + break; + } +} + +export function executeHelpConsoleCommand(bladeburner: IBladeburner, args: string[]): void { + if (args.length === 1) { + for(const line of ConsoleHelpText.helpList){ + bladeburner.postToConsole(line); + } + } else { + for (let i = 1; i < args.length; ++i) { + if(!(args[i] in ConsoleHelpText)) continue; + const helpText = ConsoleHelpText[args[i]]; + for(const line of helpText){ + bladeburner.postToConsole(line); + } + } + } +} + +export function executeAutomateConsoleCommand(bladeburner: IBladeburner, args: string[]): void { + if (args.length !== 2 && args.length !== 4) { + bladeburner.postToConsole("Invalid use of 'automate' command: automate [var] [val] [hi/low]. Use 'help automate' for more info"); + return; + } + + // Enable/Disable + if (args.length === 2) { + const flag = args[1]; + if (flag.toLowerCase() === "status") { + bladeburner.postToConsole("Automation: " + (bladeburner.automateEnabled ? "enabled" : "disabled")); + if (bladeburner.automateEnabled) { + bladeburner.postToConsole("When your stamina drops to " + formatNumber(bladeburner.automateThreshLow, 0) + + ", you will automatically switch to " + bladeburner.automateActionLow.name + + ". When your stamina recovers to " + + formatNumber(bladeburner.automateThreshHigh, 0) + ", you will automatically " + + "switch to " + bladeburner.automateActionHigh.name + "."); + } + + } else if (flag.toLowerCase().includes("en")) { + if (!(bladeburner.automateActionLow instanceof ActionIdentifier) || + !(bladeburner.automateActionHigh instanceof ActionIdentifier)) { + return bladeburner.log("Failed to enable automation. Actions were not set"); + } + bladeburner.automateEnabled = true; + bladeburner.log("Bladeburner automation enabled"); + } else if (flag.toLowerCase().includes("d")) { + bladeburner.automateEnabled = false; + bladeburner.log("Bladeburner automation disabled"); + } else { + bladeburner.log("Invalid argument for 'automate' console command: " + args[1]); + } + return; + } + + // Set variables + if (args.length === 4) { + const variable = args[1]; + const val = args[2]; + + let highLow = false; // True for high, false for low + if (args[3].toLowerCase().includes("hi")) {highLow = true;} + + switch (variable) { + case "general": + case "gen": + if (GeneralActions[val] != null) { + const action = new ActionIdentifier({ + type:ActionTypes[val], name:val, + }); + if (highLow) { + bladeburner.automateActionHigh = action; + } else { + bladeburner.automateActionLow = action; + } + bladeburner.log("Automate (" + (highLow ? "HIGH" : "LOW") + ") action set to " + val); + } else { + bladeburner.postToConsole("Invalid action name specified: " + val); + } + break; + case "contract": + case "contracts": + if (bladeburner.contracts[val] != null) { + const action = new ActionIdentifier({ + type:ActionTypes.Contract, name:val, + }); + if (highLow) { + bladeburner.automateActionHigh = action; + } else { + bladeburner.automateActionLow = action; + } + bladeburner.log("Automate (" + (highLow ? "HIGH" : "LOW") + ") action set to " + val); + } else { + bladeburner.postToConsole("Invalid contract name specified: " + val); + } + break; + case "ops": + case "op": + case "operations": + case "operation": + if (bladeburner.operations[val] != null) { + const action = new ActionIdentifier({ + type:ActionTypes.Operation, name:val, + }); + if (highLow) { + bladeburner.automateActionHigh = action; + } else { + bladeburner.automateActionLow = action; + } + bladeburner.log("Automate (" + (highLow ? "HIGH" : "LOW") + ") action set to " + val); + } else { + bladeburner.postToConsole("Invalid Operation name specified: " + val); + } + break; + case "stamina": + if (isNaN(parseFloat(val))) { + bladeburner.postToConsole("Invalid value specified for stamina threshold (must be numeric): " + val); + } else { + if (highLow) { + bladeburner.automateThreshHigh = Number(val); + } else { + bladeburner.automateThreshLow = Number(val); + } + bladeburner.log("Automate (" + (highLow ? "HIGH" : "LOW") + ") stamina threshold set to " + val); + } + break; + default: + break; + } + + return; + } +} + +export function parseCommandArguments(command: string): string[] { + /** + * Returns an array with command and its arguments in each index. + * e.g. skill "blade's intuition" foo returns [skill, blade's intuition, foo] + * The input to the fn will be trimmed and will have all whitespace replaced w/ a single space + */ + const args = []; + let start = 0; + let i = 0; + while (i < command.length) { + const c = command.charAt(i); + if (c === '"') { // Double quotes + const endQuote = command.indexOf('"', i+1); + if (endQuote !== -1 && (endQuote === command.length-1 || command.charAt(endQuote+1) === " ")) { + args.push(command.substr(i+1, (endQuote - i - 1))); + if (endQuote === command.length-1) { + start = i = endQuote+1; + } else { + start = i = endQuote+2; // Skip the space + } + continue; + } + } else if (c === "'") { // Single quotes, same thing as above + const endQuote = command.indexOf("'", i+1); + if (endQuote !== -1 && (endQuote === command.length-1 || command.charAt(endQuote+1) === " ")) { + args.push(command.substr(i+1, (endQuote - i - 1))); + if (endQuote === command.length-1) { + start = i = endQuote+1; + } else { + start = i = endQuote+2; // Skip the space + } + continue; + } + } else if (c === " ") { + args.push(command.substr(start, i-start)); + start = i+1; + } + ++i; + } + if (start !== i) {args.push(command.substr(start, i-start));} + return args; +} + +export function executeConsoleCommand(bladeburner: IBladeburner, command: string) { + command = command.trim(); + command = command.replace(/\s\s+/g, ' '); // Replace all whitespace w/ a single space + + const args = parseCommandArguments(command); + if (args.length <= 0) return; // Log an error? + + switch(args[0].toLowerCase()) { + case "automate": + executeAutomateConsoleCommand(bladeburner, args); + break; + case "clear": + case "cls": + clearConsole(bladeburner); + break; + case "help": + executeHelpConsoleCommand(bladeburner, args); + break; + case "log": + executeLogConsoleCommand(bladeburner, args); + break; + case "skill": + executeSkillConsoleCommand(bladeburner, args); + break; + case "start": + executeStartConsoleCommand(bladeburner, args); + break; + case "stop": + bladeburner.resetAction(); + break; + default: + bladeburner.postToConsole("Invalid console command"); + break; + } +} + +// Handles a potential series of commands (comm1; comm2; comm3;) +export function executeConsoleCommands(bladeburner: IBladeburner, commands: string): void { + try { + // Console History + if (bladeburner.consoleHistory[bladeburner.consoleHistory.length-1] != commands) { + bladeburner.consoleHistory.push(commands); + if (bladeburner.consoleHistory.length > 50) { + bladeburner.consoleHistory.splice(0, 1); + } + } + + const arrayOfCommands = commands.split(";"); + for (let i = 0; i < arrayOfCommands.length; ++i) { + executeConsoleCommand(bladeburner, arrayOfCommands[i]); + } + } catch(e) { + exceptionAlert(e); + } +} + +export function clearConsole(bladeburner: IBladeburner): void { + bladeburner.consoleLogs.length = 0; +} + +export function triggerMigration(bladeburner: IBladeburner, sourceCityName: string): void { + let destCityName = BladeburnerConstants.CityNames[getRandomInt(0, 5)]; + while (destCityName === sourceCityName) { + destCityName = BladeburnerConstants.CityNames[getRandomInt(0, 5)]; + } + const destCity = bladeburner.cities[destCityName]; + const sourceCity = bladeburner.cities[sourceCityName]; + if (destCity == null || sourceCity == null) { + throw new Error("Failed to find City with name: " + destCityName); + } + const rand = Math.random(); + let percentage = getRandomInt(3, 15) / 100; + + if (rand < 0.05 && sourceCity.comms > 0) { // 5% chance for community migration + percentage *= getRandomInt(2, 4); // Migration increases population change + --sourceCity.comms; + ++destCity.comms; + } + const count = Math.round(sourceCity.pop * percentage); + sourceCity.pop -= count; + destCity.pop += count; +} + +export function triggerPotentialMigration(bladeburner: IBladeburner, sourceCityName: string, chance: number): void { + if (chance == null || isNaN(chance)) { + console.error("Invalid 'chance' parameter passed into Bladeburner.triggerPotentialMigration()"); + } + if (chance > 1) {chance /= 100;} + if (Math.random() < chance) {triggerMigration(bladeburner, sourceCityName);} +} + +export function randomEvent(bladeburner: IBladeburner): void { + const chance = Math.random(); + + // Choose random source/destination city for events + const sourceCityName = BladeburnerConstants.CityNames[getRandomInt(0, 5)]; + const sourceCity = bladeburner.cities[sourceCityName]; + if (!(sourceCity instanceof City)) { + throw new Error("sourceCity was not a City object in Bladeburner.randomEvent()"); + } + + let destCityName = BladeburnerConstants.CityNames[getRandomInt(0, 5)]; + while (destCityName === sourceCityName) { + destCityName = BladeburnerConstants.CityNames[getRandomInt(0, 5)]; + } + const destCity = bladeburner.cities[destCityName]; + + if (!(sourceCity instanceof City) || !(destCity instanceof City)) { + throw new Error("sourceCity/destCity was not a City object in Bladeburner.randomEvent()"); + } + + if (chance <= 0.05) { + // New Synthoid Community, 5% + ++sourceCity.comms; + const percentage = getRandomInt(10, 20) / 100; + const count = Math.round(sourceCity.pop * percentage); + sourceCity.pop += count; + if (bladeburner.logging.events) { + bladeburner.log("Intelligence indicates that a new Synthoid community was formed in a city"); + } + } else if (chance <= 0.1) { + // Synthoid Community Migration, 5% + if (sourceCity.comms <= 0) { + // If no comms in source city, then instead trigger a new Synthoid community event + ++sourceCity.comms; + const percentage = getRandomInt(10, 20) / 100; + const count = Math.round(sourceCity.pop * percentage); + sourceCity.pop += count; + if (bladeburner.logging.events) { + bladeburner.log("Intelligence indicates that a new Synthoid community was formed in a city"); + } + } else { + --sourceCity.comms; + ++destCity.comms; + + // Change pop + const percentage = getRandomInt(10, 20) / 100; + const count = Math.round(sourceCity.pop * percentage); + sourceCity.pop -= count; + destCity.pop += count; + + if (bladeburner.logging.events) { + bladeburner.log("Intelligence indicates that a Synthoid community migrated from " + sourceCityName + " to some other city"); + } + } + } else if (chance <= 0.3) { + // New Synthoids (non community), 20% + const percentage = getRandomInt(8, 24) / 100; + const count = Math.round(sourceCity.pop * percentage); + sourceCity.pop += count; + if (bladeburner.logging.events) { + bladeburner.log("Intelligence indicates that the Synthoid population of " + sourceCityName + " just changed significantly"); + } + } else if (chance <= 0.5) { + // Synthoid migration (non community) 20% + triggerMigration(bladeburner, sourceCityName); + if (bladeburner.logging.events) { + bladeburner.log("Intelligence indicates that a large number of Synthoids migrated from " + sourceCityName + " to some other city"); + } + } else if (chance <= 0.7) { + // Synthoid Riots (+chaos), 20% + sourceCity.chaos += 1; + sourceCity.chaos *= (1 + getRandomInt(5, 20) / 100); + if (bladeburner.logging.events) { + bladeburner.log("Tensions between Synthoids and humans lead to riots in " + sourceCityName + "! Chaos increased"); + } + } else if (chance <= 0.9) { + // Less Synthoids, 20% + const percentage = getRandomInt(8, 20) / 100; + const count = Math.round(sourceCity.pop * percentage); + sourceCity.pop -= count; + if (bladeburner.logging.events) { + bladeburner.log("Intelligence indicates that the Synthoid population of " + sourceCityName + " just changed significantly"); + } + } + // 10% chance of nothing happening +} + + +/** + * Process stat gains from Contracts, Operations, and Black Operations + * @param action(Action obj) - Derived action class + * @param success(bool) - Whether action was successful + */ +export function gainActionStats(bladeburner: IBladeburner, player: IPlayer, action: IAction, success: boolean): void { + const difficulty = action.getDifficulty(); + + /** + * Gain multiplier based on difficulty. If it changes then the + * same variable calculated in completeAction() needs to change too + */ + const difficultyMult = Math.pow(difficulty, BladeburnerConstants.DiffMultExponentialFactor) + difficulty / BladeburnerConstants.DiffMultLinearFactor; + + const time = bladeburner.actionTimeToComplete; + const successMult = success ? 1 : 0.5; + + const unweightedGain = time * BladeburnerConstants.BaseStatGain * successMult * difficultyMult; + const unweightedIntGain = time * BladeburnerConstants.BaseIntGain * successMult * difficultyMult; + const skillMult = bladeburner.skillMultipliers.expGain; + player.gainHackingExp(unweightedGain * action.weights.hack * player.hacking_exp_mult * skillMult); + player.gainStrengthExp(unweightedGain * action.weights.str * player.strength_exp_mult * skillMult); + player.gainDefenseExp(unweightedGain * action.weights.def * player.defense_exp_mult * skillMult); + player.gainDexterityExp(unweightedGain * action.weights.dex * player.dexterity_exp_mult * skillMult); + player.gainAgilityExp(unweightedGain * action.weights.agi * player.agility_exp_mult * skillMult); + player.gainCharismaExp(unweightedGain * action.weights.cha * player.charisma_exp_mult * skillMult); + let intExp = unweightedIntGain * action.weights.int * skillMult; + if (intExp > 1) { + intExp = Math.pow(intExp, 0.8); + } + player.gainIntelligenceExp(intExp); +} + +export function getDiplomacyEffectiveness(bladeburner: IBladeburner, player: IPlayer): number { + // Returns a decimal by which the city's chaos level should be multiplied (e.g. 0.98) + const CharismaLinearFactor = 1e3; + const CharismaExponentialFactor = 0.045; + + const charismaEff = Math.pow(player.charisma, CharismaExponentialFactor) + player.charisma / CharismaLinearFactor; + return (100 - charismaEff) / 100; +} + +export function getRecruitmentSuccessChance(bladeburner: IBladeburner, player: IPlayer): number { + return Math.pow(player.charisma, 0.45) / (bladeburner.teamSize + 1); +} + +export function getRecruitmentTime(bladeburner: IBladeburner, player: IPlayer): number { + const effCharisma = player.charisma * bladeburner.skillMultipliers.effCha; + const charismaFactor = Math.pow(effCharisma, 0.81) + effCharisma / 90; + return Math.max(10, Math.round(BladeburnerConstants.BaseRecruitmentTimeNeeded - charismaFactor)); +} diff --git a/src/Bladeburner/IBladeburner.ts b/src/Bladeburner/IBladeburner.ts index debaafcc5..1e2f444a2 100644 --- a/src/Bladeburner/IBladeburner.ts +++ b/src/Bladeburner/IBladeburner.ts @@ -29,9 +29,9 @@ 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[]; @@ -41,5 +41,8 @@ export interface IBladeburner { startAction(action: IActionIdentifier): void; upgradeSkill(skill: Skill): void; executeConsoleCommands(command: string): void; - postToConsole(input: string, saveToLogs: boolean): void; + postToConsole(input: string, saveToLogs?: boolean): void; + log(input: string): void; + resetAction(): void; + clearConsole(): void; } \ No newline at end of file diff --git a/src/Bladeburner/data/Help.ts b/src/Bladeburner/data/Help.ts index b08f86f10..b2d085681 100644 --- a/src/Bladeburner/data/Help.ts +++ b/src/Bladeburner/data/Help.ts @@ -1,4 +1,5 @@ export const ConsoleHelpText: { + [key: string]: string[]; helpList: string[]; automate: string[]; clear: string[]; diff --git a/src/Bladeburner/ui/Console.tsx b/src/Bladeburner/ui/Console.tsx index bfe6ded6f..96bda3872 100644 --- a/src/Bladeburner/ui/Console.tsx +++ b/src/Bladeburner/ui/Console.tsx @@ -46,7 +46,7 @@ export function Console(props: IProps): React.ReactElement { const command = event.currentTarget.value; event.currentTarget.value = ""; if (command.length > 0) { - props.bladeburner.postToConsole("> " + command, true); + props.bladeburner.postToConsole("> " + command); props.bladeburner.executeConsoleCommands(command); setConsoleHistoryIndex(props.bladeburner.consoleHistory.length); rerender(); diff --git a/src/Bladeburner/ui/Stats.tsx b/src/Bladeburner/ui/Stats.tsx index 073dc7de0..672217a36 100644 --- a/src/Bladeburner/ui/Stats.tsx +++ b/src/Bladeburner/ui/Stats.tsx @@ -16,7 +16,7 @@ import { joinFaction, displayFactionContent, } from "../../Faction/FactionHelpers"; -import { IBladeburner } from "../IBladeburner"; +import { IBladeburner } from "../IBladeburner" import { TravelPopup } from "./TravelPopup"; diff --git a/src/PersonObjects/IPlayer.ts b/src/PersonObjects/IPlayer.ts index eec173dd7..4b40bfaa2 100644 --- a/src/PersonObjects/IPlayer.ts +++ b/src/PersonObjects/IPlayer.ts @@ -136,6 +136,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; From cc8de58cff0947edb5832017627af23e50214612 Mon Sep 17 00:00:00 2001 From: Olivier Gagnon Date: Mon, 16 Aug 2021 01:35:05 -0400 Subject: [PATCH 07/15] More converting blade to react. --- src/Bladeburner.jsx | 591 ++--------------------- src/Bladeburner/Bladeburner.ts | 587 +++++++++++++++++++++- src/Bladeburner/City.ts | 16 +- src/Bladeburner/IBladeburner.ts | 1 + src/Bladeburner/Skill.ts | 23 + src/Bladeburner/ui/AllPages.tsx | 10 +- src/Bladeburner/ui/BlackOpElem.tsx | 5 +- src/Bladeburner/ui/BlackOpList.tsx | 4 +- src/Bladeburner/ui/BlackOpPage.tsx | 4 +- src/Bladeburner/ui/Console.tsx | 3 + src/Bladeburner/ui/ContractElem.tsx | 9 +- src/Bladeburner/ui/ContractList.tsx | 4 +- src/Bladeburner/ui/ContractPage.tsx | 4 +- src/Bladeburner/ui/GeneralActionElem.tsx | 6 +- src/Bladeburner/ui/GeneralActionList.tsx | 4 +- src/Bladeburner/ui/GeneralActionPage.tsx | 4 +- src/Bladeburner/ui/OperationElem.tsx | 9 +- src/Bladeburner/ui/OperationList.tsx | 4 +- src/Bladeburner/ui/OperationPage.tsx | 4 +- src/Bladeburner/ui/Root.tsx | 4 +- src/RedPill.d.ts | 1 + 21 files changed, 694 insertions(+), 603 deletions(-) create mode 100644 src/RedPill.d.ts diff --git a/src/Bladeburner.jsx b/src/Bladeburner.jsx index 6b21e5f7f..850715e1c 100644 --- a/src/Bladeburner.jsx +++ b/src/Bladeburner.jsx @@ -80,6 +80,15 @@ import { getDiplomacyEffectiveness, getRecruitmentSuccessChance, getRecruitmentTime, + resetSkillMultipliers, + updateSkillMultipliers, + resetAction, + getActionObject, + completeOperation, + completeContract, + completeAction, + processAction, + startAction, } from "./Bladeburner/Bladeburner"; function Bladeburner(params={}) { @@ -116,7 +125,7 @@ function Bladeburner(params={}) { // Map of SkillNames -> level this.skills = {}; this.skillMultipliers = {}; - this.updateSkillMultipliers(); // Calls resetSkillMultipliers() + updateSkillMultipliers(this); // Calls resetSkillMultipliers() // Max Stamina is based on stats and Bladeburner-specific bonuses this.staminaBonus = 0; // Gained from training @@ -163,7 +172,7 @@ function Bladeburner(params={}) { } Bladeburner.prototype.prestige = function() { - this.resetAction(); + resetAction(this); var bladeburnerFac = Factions["Bladeburners"]; if (this.rank >= BladeburnerConstants.RankNeededForFaction) { joinFaction(bladeburnerFac); @@ -289,7 +298,6 @@ Bladeburner.prototype.storeCycles = function(numCycles=1) { this.storedCycles += numCycles; } - Bladeburner.prototype.process = function() { // Edge case condition...if Operation Daedalus is complete trigger the BitNode if (redPillFlag === false && this.blackops.hasOwnProperty("Operation Daedalus")) { @@ -308,13 +316,13 @@ Bladeburner.prototype.process = function() { dialogBoxCreate(msg); } } - this.resetAction(); + resetAction(this); } // If the Player has no Stamina, set action to idle if (this.stamina <= 0) { this.log("Your Bladeburner action was cancelled because your stamina hit 0"); - this.resetAction(); + resetAction(this); } // A 'tick' for this mechanic is one second (= 5 game cycles) @@ -352,7 +360,7 @@ Bladeburner.prototype.process = function() { this.randomEventCounter += getRandomInt(240, 600); } - this.processAction(seconds); + processAction(this, Player, seconds); // Automation if (this.automateEnabled) { @@ -360,12 +368,12 @@ Bladeburner.prototype.process = function() { if (this.stamina <= this.automateThreshLow) { if (this.action.name !== this.automateActionLow.name || this.action.type !== this.automateActionLow.type) { this.action = new ActionIdentifier({type: this.automateActionLow.type, name: this.automateActionLow.name}); - this.startAction(this.action); + startAction(this, Player, this.action); } } else if (this.stamina >= this.automateThreshHigh) { if (this.action.name !== this.automateActionHigh.name || this.action.type !== this.automateActionHigh.type) { this.action = new ActionIdentifier({type: this.automateActionHigh.type, name: this.automateActionHigh.name}); - this.startAction(this.action); + startAction(this, Player, this.action); } } } @@ -397,34 +405,6 @@ Bladeburner.prototype.calculateStaminaPenalty = function() { return Math.min(1, this.stamina / (0.5 * this.maxStamina)); } -Bladeburner.prototype.changeRank = function(change) { - if (isNaN(change)) {throw new Error("NaN passed into Bladeburner.changeRank()");} - this.rank += change; - if (this.rank < 0) {this.rank = 0;} - this.maxRank = Math.max(this.rank, this.maxRank); - - var bladeburnersFactionName = "Bladeburners"; - if (factionExists(bladeburnersFactionName)) { - var bladeburnerFac = Factions[bladeburnersFactionName]; - if (!(bladeburnerFac instanceof Faction)) { - throw new Error("Could not properly get Bladeburner Faction object in Bladeburner UI Overview Faction button"); - } - if (bladeburnerFac.isMember) { - var favorBonus = 1 + (bladeburnerFac.favor / 100); - bladeburnerFac.playerReputation += (BladeburnerConstants.RankToFactionRepFactor * change * Player.faction_rep_mult * favorBonus); - } - } - - // Gain skill points - var rankNeededForSp = (this.totalSkillPoints+1) * BladeburnerConstants.RanksPerSkillPoint; - if (this.maxRank >= rankNeededForSp) { - // Calculate how many skill points to gain - var gainedSkillPoints = Math.floor((this.maxRank - rankNeededForSp) / BladeburnerConstants.RanksPerSkillPoint + 1); - this.skillPoints += gainedSkillPoints; - this.totalSkillPoints += gainedSkillPoints; - } -} - Bladeburner.prototype.getCurrentCity = function() { var city = this.cities[this.city]; if (!(city instanceof City)) { @@ -433,55 +413,6 @@ Bladeburner.prototype.getCurrentCity = function() { return city; } -Bladeburner.prototype.resetSkillMultipliers = function() { - this.skillMultipliers = { - successChanceAll: 1, - successChanceStealth: 1, - successChanceKill: 1, - successChanceContract: 1, - successChanceOperation: 1, - successChanceEstimate: 1, - actionTime: 1, - effHack: 1, - effStr: 1, - effDef: 1, - effDex: 1, - effAgi: 1, - effCha: 1, - effInt: 1, - stamina: 1, - money: 1, - expGain: 1, - }; -} - -Bladeburner.prototype.updateSkillMultipliers = function() { - this.resetSkillMultipliers(); - for (var skillName in this.skills) { - if (this.skills.hasOwnProperty(skillName)) { - var skill = Skills[skillName]; - if (skill == null) { - throw new Error("Could not find Skill Object for: " + skillName); - } - var level = this.skills[skillName]; - if (level == null || level <= 0) {continue;} //Not upgraded - - var multiplierNames = Object.keys(this.skillMultipliers); - for (var i = 0; i < multiplierNames.length; ++i) { - var multiplierName = multiplierNames[i]; - if (skill[multiplierName] != null && !isNaN(skill[multiplierName])) { - var value = skill[multiplierName] * level; - var multiplierValue = 1 + (value / 100); - if (multiplierName === "actionTime") { - multiplierValue = 1 - (value / 100); - } - this.skillMultipliers[multiplierName] *= multiplierValue; - } - } - } - } -} - Bladeburner.prototype.upgradeSkill = function(skill) { // This does NOT handle deduction of skill points var skillName = skill.name; @@ -493,35 +424,7 @@ Bladeburner.prototype.upgradeSkill = function(skill) { if (isNaN(this.skills[skillName]) || this.skills[skillName] < 0) { throw new Error("Level of Skill " + skillName + " is invalid: " + this.skills[skillName]); } - this.updateSkillMultipliers(); -} - -Bladeburner.prototype.getActionObject = function(actionId) { - /** - * Given an ActionIdentifier object, returns the corresponding - * GeneralAction, Contract, Operation, or BlackOperation object - */ - switch (actionId.type) { - case ActionTypes["Contract"]: - return this.contracts[actionId.name]; - case ActionTypes["Operation"]: - return this.operations[actionId.name]; - case ActionTypes["BlackOp"]: - case ActionTypes["BlackOperation"]: - return BlackOperations[actionId.name]; - case ActionTypes["Training"]: - return GeneralActions["Training"]; - case ActionTypes["Field Analysis"]: - return GeneralActions["Field Analysis"]; - case ActionTypes["Recruitment"]: - return GeneralActions["Recruitment"]; - case ActionTypes["Diplomacy"]: - return GeneralActions["Diplomacy"]; - case ActionTypes["Hyperbolic Regeneration Chamber"]: - return GeneralActions["Hyperbolic Regeneration Chamber"]; - default: - return null; - } + updateSkillMultipliers(this); } // Sets the player to the "IDLE" action @@ -529,448 +432,8 @@ Bladeburner.prototype.resetAction = function() { this.action = new ActionIdentifier({type:ActionTypes.Idle}); } -Bladeburner.prototype.startAction = function(actionId) { - if (actionId == null) {return;} - this.action = actionId; - this.actionTimeCurrent = 0; - switch (actionId.type) { - case ActionTypes["Idle"]: - this.actionTimeToComplete = 0; - break; - case ActionTypes["Contract"]: - try { - var action = this.getActionObject(actionId); - if (action == null) { - throw new Error("Failed to get Contract Object for: " + actionId.name); - } - if (action.count < 1) {return this.resetAction();} - this.actionTimeToComplete = action.getActionTime(this); - } catch(e) { - exceptionAlert(e); - } - break; - case ActionTypes["Operation"]: - try { - var action = this.getActionObject(actionId); - if (action == null) { - throw new Error ("Failed to get Operation Object for: " + actionId.name); - } - if (action.count < 1) {return this.resetAction();} - if (actionId.name === "Raid" && this.getCurrentCity().commsEst === 0) {return this.resetAction();} - this.actionTimeToComplete = action.getActionTime(this); - } catch(e) { - exceptionAlert(e); - } - break; - case ActionTypes["BlackOp"]: - case ActionTypes["BlackOperation"]: - try { - // Safety measure - don't repeat BlackOps that are already done - if (this.blackops[actionId.name] != null) { - this.resetAction(); - this.log("Error: Tried to start a Black Operation that had already been completed"); - break; - } - - var action = this.getActionObject(actionId); - if (action == null) { - throw new Error("Failed to get BlackOperation object for: " + actionId.name); - } - this.actionTimeToComplete = action.getActionTime(this); - } catch(e) { - exceptionAlert(e); - } - break; - case ActionTypes["Recruitment"]: - this.actionTimeToComplete = getRecruitmentTime(this, Player); - break; - case ActionTypes["Training"]: - case ActionTypes["FieldAnalysis"]: - case ActionTypes["Field Analysis"]: - this.actionTimeToComplete = 30; - break; - case ActionTypes["Diplomacy"]: - case ActionTypes["Hyperbolic Regeneration Chamber"]: - this.actionTimeToComplete = 60; - break; - default: - throw new Error("Invalid Action Type in Bladeburner.startAction(): " + actionId.type); - break; - } -} - -Bladeburner.prototype.processAction = function(seconds) { - if (this.action.type === ActionTypes["Idle"]) {return;} - if (this.actionTimeToComplete <= 0) { - throw new Error(`Invalid actionTimeToComplete value: ${this.actionTimeToComplete}, type; ${this.action.type}`); - } - if (!(this.action instanceof ActionIdentifier)) { - throw new Error("Bladeburner.action is not an ActionIdentifier Object"); - } - - // If the previous action went past its completion time, add to the next action - // This is not added inmediatly in case the automation changes the action - this.actionTimeCurrent += seconds + this.actionTimeOverflow; - this.actionTimeOverflow = 0; - if (this.actionTimeCurrent >= this.actionTimeToComplete) { - this.actionTimeOverflow = this.actionTimeCurrent - this.actionTimeToComplete; - return this.completeAction(); - } -} - -Bladeburner.prototype.completeAction = function() { - switch (this.action.type) { - case ActionTypes["Contract"]: - case ActionTypes["Operation"]: - try { - var isOperation = (this.action.type === ActionTypes["Operation"]); - var action = this.getActionObject(this.action); - if (action == null) { - throw new Error("Failed to get Contract/Operation Object for: " + this.action.name); - } - var difficulty = action.getDifficulty(); - var difficultyMultiplier = Math.pow(difficulty, BladeburnerConstants.DiffMultExponentialFactor) + difficulty / BladeburnerConstants.DiffMultLinearFactor; - var rewardMultiplier = Math.pow(action.rewardFac, action.level-1); - - // Stamina loss is based on difficulty - this.stamina -= (BladeburnerConstants.BaseStaminaLoss * difficultyMultiplier); - if (this.stamina < 0) {this.stamina = 0;} - - // Process Contract/Operation success/failure - if (action.attempt(this)) { - gainActionStats(this, Player, action, true); - ++action.successes; - --action.count; - - // Earn money for contracts - var moneyGain = 0; - if (!isOperation) { - moneyGain = BladeburnerConstants.ContractBaseMoneyGain * rewardMultiplier * this.skillMultipliers.money; - Player.gainMoney(moneyGain); - Player.recordMoneySource(moneyGain, "bladeburner"); - } - - if (isOperation) { - action.setMaxLevel(BladeburnerConstants.OperationSuccessesPerLevel); - } else { - action.setMaxLevel(BladeburnerConstants.ContractSuccessesPerLevel); - } - if (action.rankGain) { - var gain = addOffset(action.rankGain * rewardMultiplier * BitNodeMultipliers.BladeburnerRank, 10); - this.changeRank(gain); - if (isOperation && this.logging.ops) { - this.log(action.name + " successfully completed! Gained " + formatNumber(gain, 3) + " rank"); - } else if (!isOperation && this.logging.contracts) { - this.log(action.name + " contract successfully completed! Gained " + formatNumber(gain, 3) + " rank and " + numeralWrapper.formatMoney(moneyGain)); - } - } - isOperation ? this.completeOperation(true) : this.completeContract(true); - } else { - gainActionStats(this, Player, action, false); - ++action.failures; - var loss = 0, damage = 0; - if (action.rankLoss) { - loss = addOffset(action.rankLoss * rewardMultiplier, 10); - this.changeRank(-1 * loss); - } - if (action.hpLoss) { - damage = action.hpLoss * difficultyMultiplier; - damage = Math.ceil(addOffset(damage, 10)); - this.hpLost += damage; - const cost = calculateHospitalizationCost(Player, damage); - if (Player.takeDamage(damage)) { - ++this.numHosp; - this.moneyLost += cost; - } - } - var logLossText = ""; - if (loss > 0) {logLossText += "Lost " + formatNumber(loss, 3) + " rank. ";} - if (damage > 0) {logLossText += "Took " + formatNumber(damage, 0) + " damage.";} - if (isOperation && this.logging.ops) { - this.log(action.name + " failed! " + logLossText); - } else if (!isOperation && this.logging.contracts) { - this.log(action.name + " contract failed! " + logLossText); - } - isOperation ? this.completeOperation(false) : this.completeContract(false); - } - if (action.autoLevel) {action.level = action.maxLevel;} // Autolevel - this.startAction(this.action); // Repeat action - } catch(e) { - exceptionAlert(e); - } - break; - case ActionTypes["BlackOp"]: - case ActionTypes["BlackOperation"]: - try { - var action = this.getActionObject(this.action); - if (action == null || !(action instanceof BlackOperation)) { - throw new Error("Failed to get BlackOperation Object for: " + this.action.name); - } - var difficulty = action.getDifficulty(); - var difficultyMultiplier = Math.pow(difficulty, BladeburnerConstants.DiffMultExponentialFactor) + difficulty / BladeburnerConstants.DiffMultLinearFactor; - - // Stamina loss is based on difficulty - this.stamina -= (BladeburnerConstants.BaseStaminaLoss * difficultyMultiplier); - if (this.stamina < 0) {this.stamina = 0;} - - // Team loss variables - var teamCount = action.teamCount, teamLossMax; - - if (action.attempt(this)) { - gainActionStats(this, Player, action, true); - action.count = 0; - this.blackops[action.name] = true; - var rankGain = 0; - if (action.rankGain) { - rankGain = addOffset(action.rankGain * BitNodeMultipliers.BladeburnerRank, 10); - this.changeRank(rankGain); - } - teamLossMax = Math.ceil(teamCount/2); - - // Operation Daedalus - if (action.name === "Operation Daedalus") { - this.resetAction(); - return hackWorldDaemon(Player.bitNodeN); - } - - if (this.logging.blackops) { - this.log(action.name + " successful! Gained " + formatNumber(rankGain, 1) + " rank"); - } - } else { - gainActionStats(this, Player, action, false); - var rankLoss = 0, damage = 0; - if (action.rankLoss) { - rankLoss = addOffset(action.rankLoss, 10); - this.changeRank(-1 * rankLoss); - } - if (action.hpLoss) { - damage = action.hpLoss * difficultyMultiplier; - damage = Math.ceil(addOffset(damage, 10)); - const cost = calculateHospitalizationCost(Player, damage); - if (Player.takeDamage(damage)) { - ++this.numHosp; - this.moneyLost += cost; - } - } - teamLossMax = Math.floor(teamCount); - - if (this.logging.blackops) { - this.log(action.name + " failed! Lost " + formatNumber(rankLoss, 1) + " rank and took " + formatNumber(damage, 0) + " damage"); - } - } - - this.resetAction(); // Stop regardless of success or fail - - // Calculate team lossses - if (teamCount >= 1) { - var losses = getRandomInt(1, teamLossMax); - this.teamSize -= losses; - this.teamLost += losses; - if (this.logging.blackops) { - this.log("You lost " + formatNumber(losses, 0) + " team members during " + action.name); - } - } - } catch(e) { - exceptionAlert(e); - } - break; - case ActionTypes["Training"]: - this.stamina -= (0.5 * BladeburnerConstants.BaseStaminaLoss); - var strExpGain = 30 * Player.strength_exp_mult, - defExpGain = 30 * Player.defense_exp_mult, - dexExpGain = 30 * Player.dexterity_exp_mult, - agiExpGain = 30 * Player.agility_exp_mult, - staminaGain = 0.04 * this.skillMultipliers.stamina; - Player.gainStrengthExp(strExpGain); - Player.gainDefenseExp(defExpGain); - Player.gainDexterityExp(dexExpGain); - Player.gainAgilityExp(agiExpGain); - this.staminaBonus += (staminaGain); - if (this.logging.general) { - this.log("Training completed. Gained: " + - formatNumber(strExpGain, 1) + " str exp, " + - formatNumber(defExpGain, 1) + " def exp, " + - formatNumber(dexExpGain, 1) + " dex exp, " + - formatNumber(agiExpGain, 1) + " agi exp, " + - formatNumber(staminaGain, 3) + " max stamina"); - } - this.startAction(this.action); // Repeat action - break; - case ActionTypes["FieldAnalysis"]: - case ActionTypes["Field Analysis"]: - // Does not use stamina. Effectiveness depends on hacking, int, and cha - var eff = 0.04 * Math.pow(Player.hacking_skill, 0.3) + - 0.04 * Math.pow(Player.intelligence, 0.9) + - 0.02 * Math.pow(Player.charisma, 0.3); - eff *= Player.bladeburner_analysis_mult; - if (isNaN(eff) || eff < 0) { - throw new Error("Field Analysis Effectiveness calculated to be NaN or negative"); - } - var hackingExpGain = 20 * Player.hacking_exp_mult, - charismaExpGain = 20 * Player.charisma_exp_mult; - Player.gainHackingExp(hackingExpGain); - Player.gainIntelligenceExp(BladeburnerConstants.BaseIntGain); - Player.gainCharismaExp(charismaExpGain); - this.changeRank(0.1 * BitNodeMultipliers.BladeburnerRank); - this.getCurrentCity().improvePopulationEstimateByPercentage(eff * this.skillMultipliers.successChanceEstimate); - if (this.logging.general) { - this.log("Field analysis completed. Gained 0.1 rank, " + formatNumber(hackingExpGain, 1) + " hacking exp, and " + formatNumber(charismaExpGain, 1) + " charisma exp"); - } - this.startAction(this.action); // Repeat action - break; - case ActionTypes["Recruitment"]: - var successChance = getRecruitmentSuccessChance(this, Player); - if (Math.random() < successChance) { - var expGain = 2 * BladeburnerConstants.BaseStatGain * this.actionTimeToComplete; - Player.gainCharismaExp(expGain); - ++this.teamSize; - if (this.logging.general) { - this.log("Successfully recruited a team member! Gained " + formatNumber(expGain, 1) + " charisma exp"); - } - } else { - var expGain = BladeburnerConstants.BaseStatGain * this.actionTimeToComplete; - Player.gainCharismaExp(expGain); - if (this.logging.general) { - this.log("Failed to recruit a team member. Gained " + formatNumber(expGain, 1) + " charisma exp"); - } - } - this.startAction(this.action); // Repeat action - break; - case ActionTypes["Diplomacy"]: - var eff = getDiplomacyEffectiveness(this, Player); - this.getCurrentCity().chaos *= eff; - if (this.getCurrentCity().chaos < 0) { this.getCurrentCity().chaos = 0; } - if (this.logging.general) { - this.log(`Diplomacy completed. Chaos levels in the current city fell by ${numeralWrapper.formatPercentage(1 - eff)}`); - } - this.startAction(this.action); // Repeat Action - break; - case ActionTypes["Hyperbolic Regeneration Chamber"]: { - Player.regenerateHp(BladeburnerConstants.HrcHpGain); - - const staminaGain = this.maxStamina * (BladeburnerConstants.HrcStaminaGain / 100); - this.stamina = Math.min(this.maxStamina, this.stamina + staminaGain); - this.startAction(this.action); - if (this.logging.general) { - this.log(`Rested in Hyperbolic Regeneration Chamber. Restored ${BladeburnerConstants.HrcHpGain} HP and gained ${numeralWrapper.formatStamina(staminaGain)} stamina`); - } - break; - } - default: - console.error(`Bladeburner.completeAction() called for invalid action: ${this.action.type}`); - break; - } -} - -Bladeburner.prototype.completeContract = function(success) { - if (this.action.type !== ActionTypes.Contract) { - throw new Error("completeContract() called even though current action is not a Contract"); - } - var city = this.getCurrentCity(); - if (success) { - switch (this.action.name) { - case "Tracking": - // Increase estimate accuracy by a relatively small amount - city.improvePopulationEstimateByCount(getRandomInt(100, 1e3)); - break; - case "Bounty Hunter": - city.changePopulationByCount(-1, {estChange:-1}); - city.changeChaosByCount(0.02); - break; - case "Retirement": - city.changePopulationByCount(-1, {estChange:-1}); - city.changeChaosByCount(0.04); - break; - default: - throw new Error("Invalid Action name in completeContract: " + this.action.name); - } - } -} - -Bladeburner.prototype.completeOperation = function(success) { - if (this.action.type !== ActionTypes.Operation) { - throw new Error("completeOperation() called even though current action is not an Operation"); - } - var action = this.getActionObject(this.action); - if (action == null) { - throw new Error("Failed to get Contract/Operation Object for: " + this.action.name); - } - - // Calculate team losses - var teamCount = action.teamCount, max; - if (teamCount >= 1) { - if (success) { - max = Math.ceil(teamCount/2); - } else { - max = Math.floor(teamCount) - } - var losses = getRandomInt(0, max); - this.teamSize -= losses; - this.teamLost += losses; - if (this.logging.ops && losses > 0) { - this.log("Lost " + formatNumber(losses, 0) + " team members during this " + action.name); - } - } - - var city = this.getCurrentCity(); - switch (action.name) { - case "Investigation": - if (success) { - city.improvePopulationEstimateByPercentage(0.4 * this.skillMultipliers.successChanceEstimate); - if (Math.random() < (0.02 * this.skillMultipliers.successChanceEstimate)) { - city.improveCommunityEstimate(1); - } - } else { - triggerPotentialMigration(this, this.city, 0.1); - } - break; - case "Undercover Operation": - if (success) { - city.improvePopulationEstimateByPercentage(0.8 * this.skillMultipliers.successChanceEstimate); - if (Math.random() < (0.02 * this.skillMultipliers.successChanceEstimate)) { - city.improveCommunityEstimate(1); - } - } else { - triggerPotentialMigration(this, this.city, 0.15); - } - break; - case "Sting Operation": - if (success) { - city.changePopulationByPercentage(-0.1, {changeEstEqually:true, nonZero:true}); - } - city.changeChaosByCount(0.1); - break; - case "Raid": - if (success) { - city.changePopulationByPercentage(-1, {changeEstEqually:true, nonZero:true}); - --city.comms; - --city.commsEst; - } else { - var change = getRandomInt(-10, -5) / 10; - city.changePopulationByPercentage(change, {nonZero:true}); - } - city.changeChaosByPercentage(getRandomInt(1, 5)); - break; - case "Stealth Retirement Operation": - if (success) { - city.changePopulationByPercentage(-0.5, {changeEstEqually:true,nonZero:true}); - } - city.changeChaosByPercentage(getRandomInt(-3, -1)); - break; - case "Assassination": - if (success) { - city.changePopulationByCount(-1, {estChange:-1}); - } - city.changeChaosByPercentage(getRandomInt(-5, 5)); - break; - default: - throw new Error("Invalid Action name in completeOperation: " + this.action.name); - } -} - - //////////////////////////////////////////////////////////////////////////////// -/////////////////////////////Unconvertable for now////////////////////////////// +//////////////////////////// Unconvertable for now ///////////////////////////// //////////////////////////////////////////////////////////////////////////////// // Bladeburner Console Window @@ -995,7 +458,7 @@ Bladeburner.prototype.executeConsoleCommands = function(commands) { } //////////////////////////////////////////////////////////////////////////////// -/////////////////////////////////Netscript Fns////////////////////////////////// +//////////////////////////////// Netscript Fns ///////////////////////////////// //////////////////////////////////////////////////////////////////////////////// Bladeburner.prototype.getTypeAndNameFromActionId = function(actionId) { @@ -1044,7 +507,7 @@ Bladeburner.prototype.startActionNetscriptFn = function(type, name, workerScript // Special logic for Black Ops if (actionId.type === ActionTypes["BlackOp"]) { // Can't start a BlackOp if you don't have the required rank - let action = this.getActionObject(actionId); + let action = getActionObject(this, actionId); if (action.reqdRank > this.rank) { workerScript.log("bladeburner.startAction", `Insufficient rank to start Black Op '${actionId.name}'.`); return false; @@ -1080,11 +543,11 @@ Bladeburner.prototype.startActionNetscriptFn = function(type, name, workerScript } try { - this.startAction(actionId); + startAction(this, Player, actionId); workerScript.log("bladeburner.startAction", `Starting bladeburner action with type '${type}' and name ${name}"`); return true; } catch(e) { - this.resetAction(); + resetAction(this); workerScript.log("bladeburner.startAction", errorLogText); return false; } @@ -1098,7 +561,7 @@ Bladeburner.prototype.getActionTimeNetscriptFn = function(type, name, workerScri return -1; } - const actionObj = this.getActionObject(actionId); + const actionObj = getActionObject(this, actionId); if (actionObj == null) { workerScript.log("bladeburner.getActionTime", errorLogText); return -1; @@ -1133,7 +596,7 @@ Bladeburner.prototype.getActionEstimatedSuccessChanceNetscriptFn = function(type return -1; } - const actionObj = this.getActionObject(actionId); + const actionObj = getActionObject(this, actionId); if (actionObj == null) { workerScript.log("bladeburner.getActionEstimatedSuccessChance", errorLogText); return -1; @@ -1165,7 +628,7 @@ Bladeburner.prototype.getActionCountRemainingNetscriptFn = function(type, name, return -1; } - const actionObj = this.getActionObject(actionId); + const actionObj = getActionObject(this, actionId); if (actionObj == null) { workerScript.log("bladeburner.getActionCountRemaining", errorLogText); return -1; @@ -1261,7 +724,7 @@ Bladeburner.prototype.getTeamSizeNetscriptFn = function(type, name, workerScript return -1; } - const actionObj = this.getActionObject(actionId); + const actionObj = getActionObject(this, actionId); if (actionObj == null) { workerScript.log("bladeburner.getTeamSize", errorLogText); return -1; @@ -1291,7 +754,7 @@ Bladeburner.prototype.setTeamSizeNetscriptFn = function(type, name, size, worker return -1; } - const actionObj = this.getActionObject(actionId); + const actionObj = getActionObject(this, actionId); if (actionObj == null) { workerScript.log("bladeburner.setTeamSize", errorLogText); return -1; diff --git a/src/Bladeburner/Bladeburner.ts b/src/Bladeburner/Bladeburner.ts index a121ed51a..067f76949 100644 --- a/src/Bladeburner/Bladeburner.ts +++ b/src/Bladeburner/Bladeburner.ts @@ -7,6 +7,7 @@ import { IActionIdentifier } from "./IActionIdentifier"; import { ActionIdentifier } from "./ActionIdentifier"; import { ActionTypes } from "./data/ActionTypes"; import { BlackOperations } from "./BlackOperations"; +import { BlackOperation } from "./BlackOperation"; import { GeneralActions } from "./GeneralActions"; import { formatNumber } from "../../utils/StringHelperFunctions"; import { Skills } from "./Skills"; @@ -18,6 +19,13 @@ import { ConsoleHelpText } from "./data/Help"; import { exceptionAlert } from "../../utils/helpers/exceptionAlert"; import { getRandomInt } from "../../utils/helpers/getRandomInt"; import { BladeburnerConstants } from "./data/Constants"; +import { numeralWrapper } from "../ui/numeralFormat"; +import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers"; +import { addOffset } from "../../utils/helpers/addOffset"; +import { Faction } from "../Faction/Faction"; +import { Factions, factionExists } from "../Faction/Factions"; +import { calculateHospitalizationCost } from "../Hospital/Hospital"; +import { hackWorldDaemon } from "../RedPill"; export function getActionIdFromTypeAndName(bladeburner: IBladeburner, type: string = "", name: string = ""): IActionIdentifier | null { if (type === "" || name === "") {return null;} @@ -104,7 +112,7 @@ export function getActionIdFromTypeAndName(bladeburner: IBladeburner, type: stri return null; } -export function executeStartConsoleCommand(bladeburner: IBladeburner, args: string[]): void { +export function executeStartConsoleCommand(bladeburner: IBladeburner, player: IPlayer, args: string[]): void { if (args.length !== 3) { bladeburner.postToConsole("Invalid usage of 'start' console command: start [type] [name]"); bladeburner.postToConsole("Use 'help start' for more info"); @@ -117,7 +125,7 @@ export function executeStartConsoleCommand(bladeburner: IBladeburner, args: stri if (GeneralActions[name] != null) { bladeburner.action.type = ActionTypes[name]; bladeburner.action.name = name; - bladeburner.startAction(bladeburner.action); + startAction(bladeburner,player, bladeburner.action); } else { bladeburner.postToConsole("Invalid action name specified: " + args[2]); } @@ -127,7 +135,7 @@ export function executeStartConsoleCommand(bladeburner: IBladeburner, args: stri if (bladeburner.contracts[name] != null) { bladeburner.action.type = ActionTypes.Contract; bladeburner.action.name = name; - bladeburner.startAction(bladeburner.action); + startAction(bladeburner,player, bladeburner.action); } else { bladeburner.postToConsole("Invalid contract name specified: " + args[2]); } @@ -139,7 +147,7 @@ export function executeStartConsoleCommand(bladeburner: IBladeburner, args: stri if (bladeburner.operations[name] != null) { bladeburner.action.type = ActionTypes.Operation; bladeburner.action.name = name; - bladeburner.startAction(bladeburner.action); + startAction(bladeburner,player, bladeburner.action); } else { bladeburner.postToConsole("Invalid Operation name specified: " + args[2]); } @@ -151,7 +159,7 @@ export function executeStartConsoleCommand(bladeburner: IBladeburner, args: stri if (BlackOperations[name] != null) { bladeburner.action.type = ActionTypes.BlackOperation; bladeburner.action.name = name; - bladeburner.startAction(bladeburner.action); + startAction(bladeburner,player, bladeburner.action); } else { bladeburner.postToConsole("Invalid BlackOp name specified: " + args[2]); } @@ -512,7 +520,7 @@ export function parseCommandArguments(command: string): string[] { return args; } -export function executeConsoleCommand(bladeburner: IBladeburner, command: string) { +export function executeConsoleCommand(bladeburner: IBladeburner, player: IPlayer, command: string) { command = command.trim(); command = command.replace(/\s\s+/g, ' '); // Replace all whitespace w/ a single space @@ -537,7 +545,7 @@ export function executeConsoleCommand(bladeburner: IBladeburner, command: string executeSkillConsoleCommand(bladeburner, args); break; case "start": - executeStartConsoleCommand(bladeburner, args); + executeStartConsoleCommand(bladeburner, player, args); break; case "stop": bladeburner.resetAction(); @@ -549,7 +557,7 @@ export function executeConsoleCommand(bladeburner: IBladeburner, command: string } // Handles a potential series of commands (comm1; comm2; comm3;) -export function executeConsoleCommands(bladeburner: IBladeburner, commands: string): void { +export function executeConsoleCommands(bladeburner: IBladeburner, player: IPlayer, commands: string): void { try { // Console History if (bladeburner.consoleHistory[bladeburner.consoleHistory.length-1] != commands) { @@ -561,7 +569,7 @@ export function executeConsoleCommands(bladeburner: IBladeburner, commands: stri const arrayOfCommands = commands.split(";"); for (let i = 0; i < arrayOfCommands.length; ++i) { - executeConsoleCommand(bladeburner, arrayOfCommands[i]); + executeConsoleCommand(bladeburner, player, arrayOfCommands[i]); } } catch(e) { exceptionAlert(e); @@ -742,3 +750,564 @@ export function getRecruitmentTime(bladeburner: IBladeburner, player: IPlayer): const charismaFactor = Math.pow(effCharisma, 0.81) + effCharisma / 90; return Math.max(10, Math.round(BladeburnerConstants.BaseRecruitmentTimeNeeded - charismaFactor)); } + +export function resetSkillMultipliers(bladeburner: IBladeburner): void { + bladeburner.skillMultipliers = { + successChanceAll: 1, + successChanceStealth: 1, + successChanceKill: 1, + successChanceContract: 1, + successChanceOperation: 1, + successChanceEstimate: 1, + actionTime: 1, + effHack: 1, + effStr: 1, + effDef: 1, + effDex: 1, + effAgi: 1, + effCha: 1, + effInt: 1, + stamina: 1, + money: 1, + expGain: 1, + }; +} + +export function updateSkillMultipliers(bladeburner: IBladeburner): void { + resetSkillMultipliers(bladeburner); + for (const skillName in bladeburner.skills) { + if (bladeburner.skills.hasOwnProperty(skillName)) { + const skill = Skills[skillName]; + if (skill == null) { + throw new Error("Could not find Skill Object for: " + skillName); + } + const level = bladeburner.skills[skillName]; + if (level == null || level <= 0) {continue;} //Not upgraded + + const multiplierNames = Object.keys(bladeburner.skillMultipliers); + for (let i = 0; i < multiplierNames.length; ++i) { + const multiplierName = multiplierNames[i]; + if (skill.getMultiplier(multiplierName) != null && !isNaN(skill.getMultiplier(multiplierName))) { + const value = skill.getMultiplier(multiplierName) * level; + let multiplierValue = 1 + (value / 100); + if (multiplierName === "actionTime") { + multiplierValue = 1 - (value / 100); + } + bladeburner.skillMultipliers[multiplierName] *= multiplierValue; + } + } + } + } +} + + +// Sets the player to the "IDLE" action +export function resetAction(bladeburner: IBladeburner): void { + bladeburner.action = new ActionIdentifier({type:ActionTypes.Idle}); +} + +export function completeOperation(bladeburner: IBladeburner, success: boolean): void { + if (bladeburner.action.type !== ActionTypes.Operation) { + throw new Error("completeOperation() called even though current action is not an Operation"); + } + const action = getActionObject(bladeburner, bladeburner.action); + if (action == null) { + throw new Error("Failed to get Contract/Operation Object for: " + bladeburner.action.name); + } + + // Calculate team losses + const teamCount = action.teamCount; + if (teamCount >= 1) { + let max; + if (success) { + max = Math.ceil(teamCount/2); + } else { + max = Math.floor(teamCount) + } + const losses = getRandomInt(0, max); + bladeburner.teamSize -= losses; + bladeburner.teamLost += losses; + if (bladeburner.logging.ops && losses > 0) { + bladeburner.log("Lost " + formatNumber(losses, 0) + " team members during this " + action.name); + } + } + + const city = bladeburner.getCurrentCity(); + switch (action.name) { + case "Investigation": + if (success) { + city.improvePopulationEstimateByPercentage(0.4 * bladeburner.skillMultipliers.successChanceEstimate); + if (Math.random() < (0.02 * bladeburner.skillMultipliers.successChanceEstimate)) { + city.improveCommunityEstimate(1); + } + } else { + triggerPotentialMigration(bladeburner, bladeburner.city, 0.1); + } + break; + case "Undercover Operation": + if (success) { + city.improvePopulationEstimateByPercentage(0.8 * bladeburner.skillMultipliers.successChanceEstimate); + if (Math.random() < (0.02 * bladeburner.skillMultipliers.successChanceEstimate)) { + city.improveCommunityEstimate(1); + } + } else { + triggerPotentialMigration(bladeburner, bladeburner.city, 0.15); + } + break; + case "Sting Operation": + if (success) { + city.changePopulationByPercentage(-0.1, {changeEstEqually:true, nonZero:true}); + } + city.changeChaosByCount(0.1); + break; + case "Raid": + if (success) { + city.changePopulationByPercentage(-1, {changeEstEqually:true, nonZero:true}); + --city.comms; + --city.commsEst; + } else { + const change = getRandomInt(-10, -5) / 10; + city.changePopulationByPercentage(change, {nonZero:true, changeEstEqually:false}); + } + city.changeChaosByPercentage(getRandomInt(1, 5)); + break; + case "Stealth Retirement Operation": + if (success) { + city.changePopulationByPercentage(-0.5, {changeEstEqually:true,nonZero:true}); + } + city.changeChaosByPercentage(getRandomInt(-3, -1)); + break; + case "Assassination": + if (success) { + city.changePopulationByCount(-1, {estChange:-1, estOffset: 0}); + } + city.changeChaosByPercentage(getRandomInt(-5, 5)); + break; + default: + throw new Error("Invalid Action name in completeOperation: " + bladeburner.action.name); + } +} + +export function getActionObject(bladeburner: IBladeburner, actionId: IActionIdentifier): IAction | null { + /** + * Given an ActionIdentifier object, returns the corresponding + * GeneralAction, Contract, Operation, or BlackOperation object + */ + switch (actionId.type) { + case ActionTypes["Contract"]: + return bladeburner.contracts[actionId.name]; + case ActionTypes["Operation"]: + return bladeburner.operations[actionId.name]; + case ActionTypes["BlackOp"]: + case ActionTypes["BlackOperation"]: + return BlackOperations[actionId.name]; + case ActionTypes["Training"]: + return GeneralActions["Training"]; + case ActionTypes["Field Analysis"]: + return GeneralActions["Field Analysis"]; + case ActionTypes["Recruitment"]: + return GeneralActions["Recruitment"]; + case ActionTypes["Diplomacy"]: + return GeneralActions["Diplomacy"]; + case ActionTypes["Hyperbolic Regeneration Chamber"]: + return GeneralActions["Hyperbolic Regeneration Chamber"]; + default: + return null; + } +} + +export function completeContract(bladeburner: IBladeburner, success: boolean): void { + if (bladeburner.action.type !== ActionTypes.Contract) { + throw new Error("completeContract() called even though current action is not a Contract"); + } + var city = bladeburner.getCurrentCity(); + if (success) { + switch (bladeburner.action.name) { + case "Tracking": + // Increase estimate accuracy by a relatively small amount + city.improvePopulationEstimateByCount(getRandomInt(100, 1e3)); + break; + case "Bounty Hunter": + city.changePopulationByCount(-1, {estChange:-1, estOffset: 0}); + city.changeChaosByCount(0.02); + break; + case "Retirement": + city.changePopulationByCount(-1, {estChange:-1, estOffset: 0}); + city.changeChaosByCount(0.04); + break; + default: + throw new Error("Invalid Action name in completeContract: " + bladeburner.action.name); + } + } +} + +export function completeAction(bladeburner: IBladeburner, player: IPlayer): void { + switch (bladeburner.action.type) { + case ActionTypes["Contract"]: + case ActionTypes["Operation"]: { + try { + const isOperation = (bladeburner.action.type === ActionTypes["Operation"]); + const action = getActionObject(bladeburner, bladeburner.action); + if (action == null) { + throw new Error("Failed to get Contract/Operation Object for: " + bladeburner.action.name); + } + const difficulty = action.getDifficulty(); + const difficultyMultiplier = Math.pow(difficulty, BladeburnerConstants.DiffMultExponentialFactor) + difficulty / BladeburnerConstants.DiffMultLinearFactor; + const rewardMultiplier = Math.pow(action.rewardFac, action.level-1); + + // Stamina loss is based on difficulty + bladeburner.stamina -= (BladeburnerConstants.BaseStaminaLoss * difficultyMultiplier); + if (bladeburner.stamina < 0) {bladeburner.stamina = 0;} + + // Process Contract/Operation success/failure + if (action.attempt(bladeburner)) { + gainActionStats(bladeburner, player, action, true); + ++action.successes; + --action.count; + + // Earn money for contracts + let moneyGain = 0; + if (!isOperation) { + moneyGain = BladeburnerConstants.ContractBaseMoneyGain * rewardMultiplier * bladeburner.skillMultipliers.money; + player.gainMoney(moneyGain); + player.recordMoneySource(moneyGain, "bladeburner"); + } + + if (isOperation) { + action.setMaxLevel(BladeburnerConstants.OperationSuccessesPerLevel); + } else { + action.setMaxLevel(BladeburnerConstants.ContractSuccessesPerLevel); + } + if (action.rankGain) { + const gain = addOffset(action.rankGain * rewardMultiplier * BitNodeMultipliers.BladeburnerRank, 10); + changeRank(bladeburner, player, gain); + if (isOperation && bladeburner.logging.ops) { + bladeburner.log(action.name + " successfully completed! Gained " + formatNumber(gain, 3) + " rank"); + } else if (!isOperation && bladeburner.logging.contracts) { + bladeburner.log(action.name + " contract successfully completed! Gained " + formatNumber(gain, 3) + " rank and " + numeralWrapper.formatMoney(moneyGain)); + } + } + isOperation ? completeOperation(bladeburner, true) : completeContract(bladeburner, true); + } else { + gainActionStats(bladeburner, player, action, false); + ++action.failures; + let loss = 0, damage = 0; + if (action.rankLoss) { + loss = addOffset(action.rankLoss * rewardMultiplier, 10); + changeRank(bladeburner, player, -1 * loss); + } + if (action.hpLoss) { + damage = action.hpLoss * difficultyMultiplier; + damage = Math.ceil(addOffset(damage, 10)); + bladeburner.hpLost += damage; + const cost = calculateHospitalizationCost(player, damage); + if (player.takeDamage(damage)) { + ++bladeburner.numHosp; + bladeburner.moneyLost += cost; + } + } + let logLossText = ""; + if (loss > 0) {logLossText += "Lost " + formatNumber(loss, 3) + " rank. ";} + if (damage > 0) {logLossText += "Took " + formatNumber(damage, 0) + " damage.";} + if (isOperation && bladeburner.logging.ops) { + bladeburner.log(action.name + " failed! " + logLossText); + } else if (!isOperation && bladeburner.logging.contracts) { + bladeburner.log(action.name + " contract failed! " + logLossText); + } + isOperation ? completeOperation(bladeburner, false) : completeContract(bladeburner, false); + } + if (action.autoLevel) {action.level = action.maxLevel;} // Autolevel + startAction(bladeburner,player, bladeburner.action); // Repeat action + } catch(e) { + exceptionAlert(e); + } + break; + } + case ActionTypes["BlackOp"]: + case ActionTypes["BlackOperation"]: { + try { + const action = getActionObject(bladeburner, bladeburner.action); + if (action == null || !(action instanceof BlackOperation)) { + throw new Error("Failed to get BlackOperation Object for: " + bladeburner.action.name); + } + const difficulty = action.getDifficulty(); + const difficultyMultiplier = Math.pow(difficulty, BladeburnerConstants.DiffMultExponentialFactor) + difficulty / BladeburnerConstants.DiffMultLinearFactor; + + // Stamina loss is based on difficulty + bladeburner.stamina -= (BladeburnerConstants.BaseStaminaLoss * difficultyMultiplier); + if (bladeburner.stamina < 0) {bladeburner.stamina = 0;} + + // Team loss variables + const teamCount = action.teamCount; + let teamLossMax; + + if (action.attempt(bladeburner)) { + gainActionStats(bladeburner, player, action, true); + action.count = 0; + bladeburner.blackops[action.name] = true; + let rankGain = 0; + if (action.rankGain) { + rankGain = addOffset(action.rankGain * BitNodeMultipliers.BladeburnerRank, 10); + changeRank(bladeburner, player, rankGain); + } + teamLossMax = Math.ceil(teamCount/2); + + // Operation Daedalus + if (action.name === "Operation Daedalus") { + resetAction(bladeburner); + return hackWorldDaemon(player.bitNodeN); + } + + if (bladeburner.logging.blackops) { + bladeburner.log(action.name + " successful! Gained " + formatNumber(rankGain, 1) + " rank"); + } + } else { + gainActionStats(bladeburner, player, action, false); + let rankLoss = 0; + let damage = 0; + if (action.rankLoss) { + rankLoss = addOffset(action.rankLoss, 10); + changeRank(bladeburner, player, -1 * rankLoss); + } + if (action.hpLoss) { + damage = action.hpLoss * difficultyMultiplier; + damage = Math.ceil(addOffset(damage, 10)); + const cost = calculateHospitalizationCost(player, damage); + if (player.takeDamage(damage)) { + ++bladeburner.numHosp; + bladeburner.moneyLost += cost; + } + } + teamLossMax = Math.floor(teamCount); + + if (bladeburner.logging.blackops) { + bladeburner.log(action.name + " failed! Lost " + formatNumber(rankLoss, 1) + " rank and took " + formatNumber(damage, 0) + " damage"); + } + } + + resetAction(bladeburner); // Stop regardless of success or fail + + // Calculate team lossses + if (teamCount >= 1) { + const losses = getRandomInt(1, teamLossMax); + bladeburner.teamSize -= losses; + bladeburner.teamLost += losses; + if (bladeburner.logging.blackops) { + bladeburner.log("You lost " + formatNumber(losses, 0) + " team members during " + action.name); + } + } + } catch(e) { + exceptionAlert(e); + } + break; + } + case ActionTypes["Training"]: { + bladeburner.stamina -= (0.5 * BladeburnerConstants.BaseStaminaLoss); + const strExpGain = 30 * player.strength_exp_mult, + defExpGain = 30 * player.defense_exp_mult, + dexExpGain = 30 * player.dexterity_exp_mult, + agiExpGain = 30 * player.agility_exp_mult, + staminaGain = 0.04 * bladeburner.skillMultipliers.stamina; + player.gainStrengthExp(strExpGain); + player.gainDefenseExp(defExpGain); + player.gainDexterityExp(dexExpGain); + player.gainAgilityExp(agiExpGain); + bladeburner.staminaBonus += (staminaGain); + if (bladeburner.logging.general) { + bladeburner.log("Training completed. Gained: " + + formatNumber(strExpGain, 1) + " str exp, " + + formatNumber(defExpGain, 1) + " def exp, " + + formatNumber(dexExpGain, 1) + " dex exp, " + + formatNumber(agiExpGain, 1) + " agi exp, " + + formatNumber(staminaGain, 3) + " max stamina"); + } + startAction(bladeburner,player, bladeburner.action); // Repeat action + break; + } + case ActionTypes["FieldAnalysis"]: + case ActionTypes["Field Analysis"]: { + // Does not use stamina. Effectiveness depends on hacking, int, and cha + let eff = 0.04 * Math.pow(player.hacking_skill, 0.3) + + 0.04 * Math.pow(player.intelligence, 0.9) + + 0.02 * Math.pow(player.charisma, 0.3); + eff *= player.bladeburner_analysis_mult; + if (isNaN(eff) || eff < 0) { + throw new Error("Field Analysis Effectiveness calculated to be NaN or negative"); + } + const hackingExpGain = 20 * player.hacking_exp_mult, + charismaExpGain = 20 * player.charisma_exp_mult; + player.gainHackingExp(hackingExpGain); + player.gainIntelligenceExp(BladeburnerConstants.BaseIntGain); + player.gainCharismaExp(charismaExpGain); + changeRank(bladeburner, player, 0.1 * BitNodeMultipliers.BladeburnerRank); + bladeburner.getCurrentCity().improvePopulationEstimateByPercentage(eff * bladeburner.skillMultipliers.successChanceEstimate); + if (bladeburner.logging.general) { + bladeburner.log("Field analysis completed. Gained 0.1 rank, " + formatNumber(hackingExpGain, 1) + " hacking exp, and " + formatNumber(charismaExpGain, 1) + " charisma exp"); + } + startAction(bladeburner,player, bladeburner.action); // Repeat action + break; + } + case ActionTypes["Recruitment"]: { + const successChance = getRecruitmentSuccessChance(bladeburner, player); + if (Math.random() < successChance) { + const expGain = 2 * BladeburnerConstants.BaseStatGain * bladeburner.actionTimeToComplete; + player.gainCharismaExp(expGain); + ++bladeburner.teamSize; + if (bladeburner.logging.general) { + bladeburner.log("Successfully recruited a team member! Gained " + formatNumber(expGain, 1) + " charisma exp"); + } + } else { + const expGain = BladeburnerConstants.BaseStatGain * bladeburner.actionTimeToComplete; + player.gainCharismaExp(expGain); + if (bladeburner.logging.general) { + bladeburner.log("Failed to recruit a team member. Gained " + formatNumber(expGain, 1) + " charisma exp"); + } + } + startAction(bladeburner,player, bladeburner.action); // Repeat action + break; + } + case ActionTypes["Diplomacy"]: { + let eff = getDiplomacyEffectiveness(bladeburner, player); + bladeburner.getCurrentCity().chaos *= eff; + if (bladeburner.getCurrentCity().chaos < 0) { bladeburner.getCurrentCity().chaos = 0; } + if (bladeburner.logging.general) { + bladeburner.log(`Diplomacy completed. Chaos levels in the current city fell by ${numeralWrapper.formatPercentage(1 - eff)}`); + } + startAction(bladeburner,player, bladeburner.action); // Repeat Action + break; + } + case ActionTypes["Hyperbolic Regeneration Chamber"]: { + player.regenerateHp(BladeburnerConstants.HrcHpGain); + + const staminaGain = bladeburner.maxStamina * (BladeburnerConstants.HrcStaminaGain / 100); + bladeburner.stamina = Math.min(bladeburner.maxStamina, bladeburner.stamina + staminaGain); + startAction(bladeburner,player, bladeburner.action); + if (bladeburner.logging.general) { + bladeburner.log(`Rested in Hyperbolic Regeneration Chamber. Restored ${BladeburnerConstants.HrcHpGain} HP and gained ${numeralWrapper.formatStamina(staminaGain)} stamina`); + } + break; + } + default: + console.error(`Bladeburner.completeAction() called for invalid action: ${bladeburner.action.type}`); + break; + } +} + +export function changeRank(bladeburner: IBladeburner, player: IPlayer, change: number): void { + if (isNaN(change)) {throw new Error("NaN passed into Bladeburner.changeRank()");} + bladeburner.rank += change; + if (bladeburner.rank < 0) {bladeburner.rank = 0;} + bladeburner.maxRank = Math.max(bladeburner.rank, bladeburner.maxRank); + + var bladeburnersFactionName = "Bladeburners"; + if (factionExists(bladeburnersFactionName)) { + var bladeburnerFac = Factions[bladeburnersFactionName]; + if (!(bladeburnerFac instanceof Faction)) { + throw new Error("Could not properly get Bladeburner Faction object in Bladeburner UI Overview Faction button"); + } + if (bladeburnerFac.isMember) { + var favorBonus = 1 + (bladeburnerFac.favor / 100); + bladeburnerFac.playerReputation += (BladeburnerConstants.RankToFactionRepFactor * change * player.faction_rep_mult * favorBonus); + } + } + + // Gain skill points + var rankNeededForSp = (bladeburner.totalSkillPoints+1) * BladeburnerConstants.RanksPerSkillPoint; + if (bladeburner.maxRank >= rankNeededForSp) { + // Calculate how many skill points to gain + var gainedSkillPoints = Math.floor((bladeburner.maxRank - rankNeededForSp) / BladeburnerConstants.RanksPerSkillPoint + 1); + bladeburner.skillPoints += gainedSkillPoints; + bladeburner.totalSkillPoints += gainedSkillPoints; + } +} + +export function processAction(bladeburner: IBladeburner, player: IPlayer, seconds: number): void { + if (bladeburner.action.type === ActionTypes["Idle"]) return; + if (bladeburner.actionTimeToComplete <= 0) { + throw new Error(`Invalid actionTimeToComplete value: ${bladeburner.actionTimeToComplete}, type; ${bladeburner.action.type}`); + } + if (!(bladeburner.action instanceof ActionIdentifier)) { + throw new Error("Bladeburner.action is not an ActionIdentifier Object"); + } + + // If the previous action went past its completion time, add to the next action + // This is not added inmediatly in case the automation changes the action + bladeburner.actionTimeCurrent += seconds + bladeburner.actionTimeOverflow; + bladeburner.actionTimeOverflow = 0; + if (bladeburner.actionTimeCurrent >= bladeburner.actionTimeToComplete) { + bladeburner.actionTimeOverflow = bladeburner.actionTimeCurrent - bladeburner.actionTimeToComplete; + return completeAction(bladeburner, player); + } +} + +export function startAction(bladeburner: IBladeburner, player: IPlayer, actionId: IActionIdentifier): void { + if (actionId == null) return; + bladeburner.action = actionId; + bladeburner.actionTimeCurrent = 0; + switch (actionId.type) { + case ActionTypes["Idle"]: + bladeburner.actionTimeToComplete = 0; + break; + case ActionTypes["Contract"]: + try { + const action = getActionObject(bladeburner, actionId); + if (action == null) { + throw new Error("Failed to get Contract Object for: " + actionId.name); + } + if (action.count < 1) {return resetAction(bladeburner);} + bladeburner.actionTimeToComplete = action.getActionTime(bladeburner); + } catch(e) { + exceptionAlert(e); + } + break; + case ActionTypes["Operation"]: { + try { + const action = getActionObject(bladeburner, actionId); + if (action == null) { + throw new Error ("Failed to get Operation Object for: " + actionId.name); + } + if (action.count < 1) {return resetAction(bladeburner);} + if (actionId.name === "Raid" && bladeburner.getCurrentCity().commsEst === 0) {return resetAction(bladeburner);} + bladeburner.actionTimeToComplete = action.getActionTime(bladeburner); + } catch(e) { + exceptionAlert(e); + } + break; + } + case ActionTypes["BlackOp"]: + case ActionTypes["BlackOperation"]: { + try { + // Safety measure - don't repeat BlackOps that are already done + if (bladeburner.blackops[actionId.name] != null) { + resetAction(bladeburner); + bladeburner.log("Error: Tried to start a Black Operation that had already been completed"); + break; + } + + const action = getActionObject(bladeburner, actionId); + if (action == null) { + throw new Error("Failed to get BlackOperation object for: " + actionId.name); + } + bladeburner.actionTimeToComplete = action.getActionTime(bladeburner); + } catch(e) { + exceptionAlert(e); + } + break; + } + case ActionTypes["Recruitment"]: + bladeburner.actionTimeToComplete = getRecruitmentTime(bladeburner, player); + break; + case ActionTypes["Training"]: + case ActionTypes["FieldAnalysis"]: + case ActionTypes["Field Analysis"]: + bladeburner.actionTimeToComplete = 30; + break; + case ActionTypes["Diplomacy"]: + case ActionTypes["Hyperbolic Regeneration Chamber"]: + bladeburner.actionTimeToComplete = 60; + break; + default: + throw new Error("Invalid Action Type in startAction(Bladeburner,player, ): " + actionId.type); + break; + } +} \ No newline at end of file diff --git a/src/Bladeburner/City.ts b/src/Bladeburner/City.ts index 75f8aff24..361593c5f 100644 --- a/src/Bladeburner/City.ts +++ b/src/Bladeburner/City.ts @@ -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)); diff --git a/src/Bladeburner/IBladeburner.ts b/src/Bladeburner/IBladeburner.ts index 1e2f444a2..b8d65909c 100644 --- a/src/Bladeburner/IBladeburner.ts +++ b/src/Bladeburner/IBladeburner.ts @@ -11,6 +11,7 @@ export interface IBladeburner { totalSkillPoints: number; teamSize: number; teamLost: number; + hpLost: number; storedCycles: number; randomEventCounter: number; actionTimeToComplete: number; diff --git a/src/Bladeburner/Skill.ts b/src/Bladeburner/Skill.ts index deb84751e..fb590efd3 100644 --- a/src/Bladeburner/Skill.ts +++ b/src/Bladeburner/Skill.ts @@ -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 1; + } } diff --git a/src/Bladeburner/ui/AllPages.tsx b/src/Bladeburner/ui/AllPages.tsx index bfdd3466d..2ead8cf2f 100644 --- a/src/Bladeburner/ui/AllPages.tsx +++ b/src/Bladeburner/ui/AllPages.tsx @@ -6,9 +6,11 @@ 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 { @@ -36,10 +38,10 @@ export function AllPages(props: IProps): React.ReactElement {
        - {page === 'General' && } - {page === 'Contracts' && } - {page === 'Operations' && } - {page === 'BlackOps' && } + {page === 'General' && } + {page === 'Contracts' && } + {page === 'Operations' && } + {page === 'BlackOps' && } {page === 'Skills' && }
        {stealthIcon}= This action requires stealth, {killIcon} = This action involves retirement diff --git a/src/Bladeburner/ui/BlackOpElem.tsx b/src/Bladeburner/ui/BlackOpElem.tsx index 5ac713359..c5b9c8f5a 100644 --- a/src/Bladeburner/ui/BlackOpElem.tsx +++ b/src/Bladeburner/ui/BlackOpElem.tsx @@ -9,9 +9,12 @@ 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"; +import { startAction } from "../Bladeburner"; interface IProps { bladeburner: IBladeburner; + player: IPlayer; action: any; } @@ -32,7 +35,7 @@ export function BlackOpElem(props: IProps): React.ReactElement { function onStart() { props.bladeburner.action.type = ActionTypes.BlackOperation; props.bladeburner.action.name = props.action.name; - props.bladeburner.startAction(props.bladeburner.action); + startAction(props.bladeburner, props.player, props.bladeburner.action); setRerender(old => !old); } diff --git a/src/Bladeburner/ui/BlackOpList.tsx b/src/Bladeburner/ui/BlackOpList.tsx index 75cfafaf1..7f35db9a9 100644 --- a/src/Bladeburner/ui/BlackOpList.tsx +++ b/src/Bladeburner/ui/BlackOpList.tsx @@ -10,9 +10,11 @@ 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 { @@ -34,7 +36,7 @@ export function BlackOpList(props: IProps): React.ReactElement { return (<> {blackops.map((blackop: BlackOperation) =>
      • - +
      • , )} ); diff --git a/src/Bladeburner/ui/BlackOpPage.tsx b/src/Bladeburner/ui/BlackOpPage.tsx index 834b0b327..73ead8d09 100644 --- a/src/Bladeburner/ui/BlackOpPage.tsx +++ b/src/Bladeburner/ui/BlackOpPage.tsx @@ -1,9 +1,11 @@ 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 { @@ -21,6 +23,6 @@ export function BlackOpPage(props: IProps): React.ReactElement { Like normal operations, you may use a team for Black Ops. Failing a black op will incur heavy HP and rank losses.

        - + ); } \ No newline at end of file diff --git a/src/Bladeburner/ui/Console.tsx b/src/Bladeburner/ui/Console.tsx index 96bda3872..8cbee7181 100644 --- a/src/Bladeburner/ui/Console.tsx +++ b/src/Bladeburner/ui/Console.tsx @@ -1,6 +1,8 @@ import React, { useState, useRef, useEffect } from "react"; import { IBladeburner } from "../IBladeburner"; +import { IPlayer } from "../../PersonObjects/IPlayer"; + interface ILineProps { content: any; } @@ -13,6 +15,7 @@ function Line(props: ILineProps): React.ReactElement { interface IProps { bladeburner: IBladeburner; + player: IPlayer; } export function Console(props: IProps): React.ReactElement { diff --git a/src/Bladeburner/ui/ContractElem.tsx b/src/Bladeburner/ui/ContractElem.tsx index 568c62cfb..789317e11 100644 --- a/src/Bladeburner/ui/ContractElem.tsx +++ b/src/Bladeburner/ui/ContractElem.tsx @@ -8,9 +8,12 @@ import { import { stealthIcon, killIcon } from "../data/Icons"; import { BladeburnerConstants } from "../data/Constants"; import { IBladeburner } from "../IBladeburner"; +import { IPlayer } from "../../PersonObjects/IPlayer"; +import { startAction } from "../Bladeburner"; interface IProps { bladeburner: IBladeburner; + player: IPlayer; action: any; } @@ -26,19 +29,19 @@ export function ContractElem(props: IProps): React.ReactElement { function onStart() { props.bladeburner.action.type = ActionTypes.Contract; props.bladeburner.action.name = props.action.name; - props.bladeburner.startAction(props.bladeburner.action); + startAction(props.bladeburner, props.player, props.bladeburner.action); setRerender(old => !old); } function increaseLevel() { ++props.action.level; - if (isActive) props.bladeburner.startAction(props.bladeburner.action); + if (isActive) startAction(props.bladeburner, props.player, props.bladeburner.action); setRerender(old => !old); } function decreaseLevel() { --props.action.level; - if (isActive) props.bladeburner.startAction(props.bladeburner.action); + if (isActive) startAction(props.bladeburner, props.player, props.bladeburner.action); setRerender(old => !old); } diff --git a/src/Bladeburner/ui/ContractList.tsx b/src/Bladeburner/ui/ContractList.tsx index 8d3c7bace..3e5087eef 100644 --- a/src/Bladeburner/ui/ContractList.tsx +++ b/src/Bladeburner/ui/ContractList.tsx @@ -6,9 +6,11 @@ import { 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 { @@ -16,7 +18,7 @@ export function ContractList(props: IProps): React.ReactElement { const contracts = props.bladeburner.contracts; return (<> {names.map((name: string) =>
      • - +
      • , )} ); diff --git a/src/Bladeburner/ui/ContractPage.tsx b/src/Bladeburner/ui/ContractPage.tsx index b151f84c2..15848d8cf 100644 --- a/src/Bladeburner/ui/ContractPage.tsx +++ b/src/Bladeburner/ui/ContractPage.tsx @@ -1,9 +1,11 @@ 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 { @@ -16,6 +18,6 @@ export function ContractPage(props: IProps): React.ReactElement { You can unlock higher-level contracts by successfully completing them. Higher-level contracts are more difficult, but grant more rank, experience, and money.

        - + ); } \ No newline at end of file diff --git a/src/Bladeburner/ui/GeneralActionElem.tsx b/src/Bladeburner/ui/GeneralActionElem.tsx index 0cadbd7c2..bd312035f 100644 --- a/src/Bladeburner/ui/GeneralActionElem.tsx +++ b/src/Bladeburner/ui/GeneralActionElem.tsx @@ -8,9 +8,13 @@ import { import { stealthIcon, killIcon } from "../data/Icons"; import { BladeburnerConstants } from "../data/Constants"; import { IBladeburner } from "../IBladeburner"; +import { IPlayer } from "../../PersonObjects/IPlayer"; + +import { startAction } from "../Bladeburner"; interface IProps { bladeburner: IBladeburner; + player: IPlayer; action: any; } @@ -22,7 +26,7 @@ export function GeneralActionElem(props: IProps): React.ReactElement { 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); + startAction(props.bladeburner, props.player, props.bladeburner.action); setRerender(old => !old); } diff --git a/src/Bladeburner/ui/GeneralActionList.tsx b/src/Bladeburner/ui/GeneralActionList.tsx index 9a2d4df08..be803bdae 100644 --- a/src/Bladeburner/ui/GeneralActionList.tsx +++ b/src/Bladeburner/ui/GeneralActionList.tsx @@ -7,9 +7,11 @@ 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 { @@ -21,7 +23,7 @@ export function GeneralActionList(props: IProps): React.ReactElement { } return (<> {actions.map((action: Action) =>
      • - +
      • , )} ); diff --git a/src/Bladeburner/ui/GeneralActionPage.tsx b/src/Bladeburner/ui/GeneralActionPage.tsx index bf87173f0..b2647a483 100644 --- a/src/Bladeburner/ui/GeneralActionPage.tsx +++ b/src/Bladeburner/ui/GeneralActionPage.tsx @@ -1,9 +1,11 @@ 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 { @@ -12,6 +14,6 @@ export function GeneralActionPage(props: IProps): React.ReactElement { These are generic actions that will assist you in your Bladeburner duties. They will not affect your Bladeburner rank in any way.

        - + ); } \ No newline at end of file diff --git a/src/Bladeburner/ui/OperationElem.tsx b/src/Bladeburner/ui/OperationElem.tsx index 20e1f2892..f3d07eb62 100644 --- a/src/Bladeburner/ui/OperationElem.tsx +++ b/src/Bladeburner/ui/OperationElem.tsx @@ -10,9 +10,12 @@ import { BladeburnerConstants } from "../data/Constants"; import { createPopup } from "../../ui/React/createPopup"; import { TeamSizePopup } from "./TeamSizePopup"; import { IBladeburner } from "../IBladeburner"; +import { IPlayer } from "../../PersonObjects/IPlayer"; +import { startAction } from "../Bladeburner"; interface IProps { bladeburner: IBladeburner; + player: IPlayer; action: any; } @@ -28,7 +31,7 @@ export function OperationElem(props: IProps): React.ReactElement { function onStart() { props.bladeburner.action.type = ActionTypes.Operation; props.bladeburner.action.name = props.action.name; - props.bladeburner.startAction(props.bladeburner.action); + startAction(props.bladeburner, props.player, props.bladeburner.action); setRerender(old => !old); } @@ -43,13 +46,13 @@ export function OperationElem(props: IProps): React.ReactElement { function increaseLevel() { ++props.action.level; - if (isActive) props.bladeburner.startAction(props.bladeburner.action); + if (isActive) startAction(props.bladeburner, props.player, props.bladeburner.action); setRerender(old => !old); } function decreaseLevel() { --props.action.level; - if (isActive) props.bladeburner.startAction(props.bladeburner.action); + if (isActive) startAction(props.bladeburner, props.player, props.bladeburner.action); setRerender(old => !old); } diff --git a/src/Bladeburner/ui/OperationList.tsx b/src/Bladeburner/ui/OperationList.tsx index 5d2387951..fa1e7e090 100644 --- a/src/Bladeburner/ui/OperationList.tsx +++ b/src/Bladeburner/ui/OperationList.tsx @@ -6,9 +6,11 @@ import { 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 { @@ -16,7 +18,7 @@ export function OperationList(props: IProps): React.ReactElement { const operations = props.bladeburner.operations; return (<> {names.map((name: string) =>
      • - +
      • , )} ); diff --git a/src/Bladeburner/ui/OperationPage.tsx b/src/Bladeburner/ui/OperationPage.tsx index 13233440d..f2560f6c0 100644 --- a/src/Bladeburner/ui/OperationPage.tsx +++ b/src/Bladeburner/ui/OperationPage.tsx @@ -1,9 +1,11 @@ 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 { @@ -27,6 +29,6 @@ export function OperationPage(props: IProps): React.ReactElement { You can unlock higher-level operations by successfully completing them. Higher-level operations are more difficult, but grant more rank and experience.

        - + ); } \ No newline at end of file diff --git a/src/Bladeburner/ui/Root.tsx b/src/Bladeburner/ui/Root.tsx index 0a1ea2881..7bb47d55e 100644 --- a/src/Bladeburner/ui/Root.tsx +++ b/src/Bladeburner/ui/Root.tsx @@ -19,10 +19,10 @@ export function Root(props: IProps): React.ReactElement {
        - +
        - +
        ); } \ No newline at end of file diff --git a/src/RedPill.d.ts b/src/RedPill.d.ts new file mode 100644 index 000000000..073ed8a6b --- /dev/null +++ b/src/RedPill.d.ts @@ -0,0 +1 @@ +export declare function hackWorldDaemon(currentNodeNumber: number, flume: boolean = false, quick: boolean = false): void; \ No newline at end of file From 4865563f26ec40fac3e22150e7fba12ffb8c72ec Mon Sep 17 00:00:00 2001 From: Olivier Gagnon Date: Mon, 16 Aug 2021 17:45:26 -0400 Subject: [PATCH 08/15] Almost done converting blade to react. --- src/Bladeburner.jsx | 660 +++------------------------------ src/Bladeburner/Bladeburner.ts | 608 +++++++++++++++++++++++++++++- src/PersonObjects/IPlayer.ts | 1 + src/RedPill.d.ts | 1 + src/engine.jsx | 3 +- 5 files changed, 661 insertions(+), 612 deletions(-) diff --git a/src/Bladeburner.jsx b/src/Bladeburner.jsx index 850715e1c..3a30b91fa 100644 --- a/src/Bladeburner.jsx +++ b/src/Bladeburner.jsx @@ -89,6 +89,32 @@ import { completeAction, processAction, startAction, + calculateStaminaGainPerSecond, + calculateMaxStamina, + create, + prestige, + storeCycles, + getCurrentCity, + upgradeSkill, + postToConsole, + log, + calculateStaminaPenalty, + getTypeAndNameFromActionId, + getContractNamesNetscriptFn, + getOperationNamesNetscriptFn, + getBlackOpNamesNetscriptFn, + getGeneralActionNamesNetscriptFn, + getSkillNamesNetscriptFn, + startActionNetscriptFn, + getActionTimeNetscriptFn, + getActionEstimatedSuccessChanceNetscriptFn, + getActionCountRemainingNetscriptFn, + getSkillLevelNetscriptFn, + getSkillUpgradeCostNetscriptFn, + upgradeSkillNetscriptFn, + getTeamSizeNetscriptFn, + setTeamSizeNetscriptFn, + joinBladeburnerFactionNetscriptFn, } from "./Bladeburner/Bladeburner"; function Bladeburner(params={}) { @@ -130,7 +156,7 @@ function Bladeburner(params={}) { // Max Stamina is based on stats and Bladeburner-specific bonuses this.staminaBonus = 0; // Gained from training this.maxStamina = 0; - this.calculateMaxStamina(); + calculateMaxStamina(this, Player); this.stamina = this.maxStamina; /** @@ -168,622 +194,40 @@ function Bladeburner(params={}) { ]; // Initialization - if (params.new) {this.create();} + if (params.new) create(this); } -Bladeburner.prototype.prestige = function() { - resetAction(this); - var bladeburnerFac = Factions["Bladeburners"]; - if (this.rank >= BladeburnerConstants.RankNeededForFaction) { - joinFaction(bladeburnerFac); - } -} - -Bladeburner.prototype.create = function() { - this.contracts["Tracking"] = new Contract({ - name:"Tracking", - desc:"Identify and locate Synthoids. This contract involves reconnaissance " + - "and information-gathering ONLY. Do NOT engage. Stealth is of the utmost importance.

        " + - "Successfully completing Tracking contracts will slightly improve your Synthoid population estimate for " + - "whatever city you are currently in.", - baseDifficulty:125,difficultyFac:1.02,rewardFac:1.041, - rankGain:0.3, hpLoss:0.5, - count:getRandomInt(25, 150), countGrowth:getRandomInt(5, 75)/10, - weights:{hack:0,str:0.05,def:0.05,dex:0.35,agi:0.35,cha:0.1, int:0.05}, - decays:{hack:0,str:0.91,def:0.91,dex:0.91,agi:0.91,cha:0.9, int:1}, - isStealth:true, - }); - this.contracts["Bounty Hunter"] = new Contract({ - name:"Bounty Hunter", - desc:"Hunt down and capture fugitive Synthoids. These Synthoids are wanted alive.

        " + - "Successfully completing a Bounty Hunter contract will lower the population in your " + - "current city, and will also increase its chaos level.", - baseDifficulty:250, difficultyFac:1.04,rewardFac:1.085, - rankGain:0.9, hpLoss:1, - count:getRandomInt(5, 150), countGrowth:getRandomInt(5, 75)/10, - weights:{hack:0,str:0.15,def:0.15,dex:0.25,agi:0.25,cha:0.1, int:0.1}, - decays:{hack:0,str:0.91,def:0.91,dex:0.91,agi:0.91,cha:0.8, int:0.9}, - isKill:true, - }); - this.contracts["Retirement"] = new Contract({ - name:"Retirement", - desc:"Hunt down and retire (kill) rogue Synthoids.

        " + - "Successfully completing a Retirement contract will lower the population in your current " + - "city, and will also increase its chaos level.", - baseDifficulty:200, difficultyFac:1.03, rewardFac:1.065, - rankGain:0.6, hpLoss:1, - count:getRandomInt(5, 150), countGrowth:getRandomInt(5, 75)/10, - weights:{hack:0,str:0.2,def:0.2,dex:0.2,agi:0.2,cha:0.1, int:0.1}, - decays:{hack:0,str:0.91,def:0.91,dex:0.91,agi:0.91,cha:0.8, int:0.9}, - isKill:true, - }); - - this.operations["Investigation"] = new Operation({ - name:"Investigation", - desc:"As a field agent, investigate and identify Synthoid " + - "populations, movements, and operations.

        Successful " + - "Investigation ops will increase the accuracy of your " + - "synthoid data.

        " + - "You will NOT lose HP from failed Investigation ops.", - baseDifficulty:400, difficultyFac:1.03,rewardFac:1.07,reqdRank:25, - rankGain:2.2, rankLoss:0.2, - count:getRandomInt(1, 100), countGrowth:getRandomInt(10, 40)/10, - weights:{hack:0.25,str:0.05,def:0.05,dex:0.2,agi:0.1,cha:0.25, int:0.1}, - decays:{hack:0.85,str:0.9,def:0.9,dex:0.9,agi:0.9,cha:0.7, int:0.9}, - isStealth:true, - }); - this.operations["Undercover Operation"] = new Operation({ - name:"Undercover Operation", - desc:"Conduct undercover operations to identify hidden " + - "and underground Synthoid communities and organizations.

        " + - "Successful Undercover ops will increase the accuracy of your synthoid " + - "data.", - baseDifficulty:500, difficultyFac:1.04, rewardFac:1.09, reqdRank:100, - rankGain:4.4, rankLoss:0.4, hpLoss:2, - count:getRandomInt(1, 100), countGrowth:getRandomInt(10, 40)/10, - weights:{hack:0.2,str:0.05,def:0.05,dex:0.2,agi:0.2,cha:0.2, int:0.1}, - decays:{hack:0.8,str:0.9,def:0.9,dex:0.9,agi:0.9,cha:0.7, int:0.9}, - isStealth:true, - }); - this.operations["Sting Operation"] = new Operation({ - name:"Sting Operation", - desc:"Conduct a sting operation to bait and capture particularly " + - "notorious Synthoid criminals.", - baseDifficulty:650, difficultyFac:1.04, rewardFac:1.095, reqdRank:500, - rankGain:5.5, rankLoss:0.5, hpLoss:2.5, - count:getRandomInt(1, 150), countGrowth:getRandomInt(3, 40)/10, - weights:{hack:0.25,str:0.05,def:0.05,dex:0.25,agi:0.1,cha:0.2, int:0.1}, - decays:{hack:0.8,str:0.85,def:0.85,dex:0.85,agi:0.85,cha:0.7, int:0.9}, - isStealth:true, - }); - this.operations["Raid"] = new Operation({ - name:"Raid", - desc:"Lead an assault on a known Synthoid community. Note that " + - "there must be an existing Synthoid community in your current city " + - "in order for this Operation to be successful.", - baseDifficulty:800, difficultyFac:1.045, rewardFac:1.1, reqdRank:3000, - rankGain:55,rankLoss:2.5,hpLoss:50, - count:getRandomInt(1, 150), countGrowth:getRandomInt(2, 40)/10, - weights:{hack:0.1,str:0.2,def:0.2,dex:0.2,agi:0.2,cha:0, int:0.1}, - decays:{hack:0.7,str:0.8,def:0.8,dex:0.8,agi:0.8,cha:0, int:0.9}, - isKill:true, - }); - this.operations["Stealth Retirement Operation"] = new Operation({ - name:"Stealth Retirement Operation", - desc:"Lead a covert operation to retire Synthoids. The " + - "objective is to complete the task without " + - "drawing any attention. Stealth and discretion are key.", - baseDifficulty:1000, difficultyFac:1.05, rewardFac:1.11, reqdRank:20e3, - rankGain:22, rankLoss:2, hpLoss:10, - count:getRandomInt(1, 150), countGrowth:getRandomInt(1, 20)/10, - weights:{hack:0.1,str:0.1,def:0.1,dex:0.3,agi:0.3,cha:0, int:0.1}, - decays:{hack:0.7,str:0.8,def:0.8,dex:0.8,agi:0.8,cha:0, int:0.9}, - isStealth:true, isKill:true, - }); - this.operations["Assassination"] = new Operation({ - name:"Assassination", - desc:"Assassinate Synthoids that have been identified as " + - "important, high-profile social and political leaders " + - "in the Synthoid communities.", - baseDifficulty:1500, difficultyFac:1.06, rewardFac:1.14, reqdRank:50e3, - rankGain:44, rankLoss:4, hpLoss:5, - count:getRandomInt(1, 150), countGrowth:getRandomInt(1, 20)/10, - weights:{hack:0.1,str:0.1,def:0.1,dex:0.3,agi:0.3,cha:0, int:0.1}, - decays:{hack:0.6,str:0.8,def:0.8,dex:0.8,agi:0.8,cha:0, int:0.8}, - isStealth:true, isKill:true, - }); -} - -Bladeburner.prototype.storeCycles = function(numCycles=1) { - this.storedCycles += numCycles; -} - -Bladeburner.prototype.process = function() { - // Edge case condition...if Operation Daedalus is complete trigger the BitNode - if (redPillFlag === false && this.blackops.hasOwnProperty("Operation Daedalus")) { - return hackWorldDaemon(Player.bitNodeN); - } - - // If the Player starts doing some other actions, set action to idle and alert - if (Augmentations[AugmentationNames.BladesSimulacrum].owned === false && Player.isWorking) { - if (this.action.type !== ActionTypes["Idle"]) { - let msg = "Your Bladeburner action was cancelled because you started doing something else."; - if (this.automateEnabled) { - msg += `

        Your automation was disabled as well. You will have to re-enable it through the Bladeburner console` - this.automateEnabled = false; - } - if (!Settings.SuppressBladeburnerPopup) { - dialogBoxCreate(msg); - } - } - resetAction(this); - } - - // If the Player has no Stamina, set action to idle - if (this.stamina <= 0) { - this.log("Your Bladeburner action was cancelled because your stamina hit 0"); - resetAction(this); - } - - // A 'tick' for this mechanic is one second (= 5 game cycles) - if (this.storedCycles >= BladeburnerConstants.CyclesPerSecond) { - var seconds = Math.floor(this.storedCycles / BladeburnerConstants.CyclesPerSecond); - seconds = Math.min(seconds, 5); // Max of 5 'ticks' - this.storedCycles -= seconds * BladeburnerConstants.CyclesPerSecond; - - // Stamina - this.calculateMaxStamina(); - this.stamina += (this.calculateStaminaGainPerSecond() * seconds); - this.stamina = Math.min(this.maxStamina, this.stamina); - - // Count increase for contracts/operations - for (let contract of Object.values(this.contracts)) { - contract.count += (seconds * contract.countGrowth/BladeburnerConstants.ActionCountGrowthPeriod); - } - for (let op of Object.values(this.operations)) { - op.count += (seconds * op.countGrowth/BladeburnerConstants.ActionCountGrowthPeriod); - } - - // Chaos goes down very slowly - for (let cityName of BladeburnerConstants.CityNames) { - var city = this.cities[cityName]; - if (!(city instanceof City)) {throw new Error("Invalid City object when processing passive chaos reduction in Bladeburner.process");} - city.chaos -= (0.0001 * seconds); - city.chaos = Math.max(0, city.chaos); - } - - // Random Events - this.randomEventCounter -= seconds; - if (this.randomEventCounter <= 0) { - randomEvent(this); - // Add instead of setting because we might have gone over the required time for the event - this.randomEventCounter += getRandomInt(240, 600); - } - - processAction(this, Player, seconds); - - // Automation - if (this.automateEnabled) { - // Note: Do NOT set this.action = this.automateActionHigh/Low since it creates a reference - if (this.stamina <= this.automateThreshLow) { - if (this.action.name !== this.automateActionLow.name || this.action.type !== this.automateActionLow.type) { - this.action = new ActionIdentifier({type: this.automateActionLow.type, name: this.automateActionLow.name}); - startAction(this, Player, this.action); - } - } else if (this.stamina >= this.automateThreshHigh) { - if (this.action.name !== this.automateActionHigh.name || this.action.type !== this.automateActionHigh.type) { - this.action = new ActionIdentifier({type: this.automateActionHigh.type, name: this.automateActionHigh.name}); - startAction(this, Player, this.action); - } - } - } - - } -} - -Bladeburner.prototype.calculateMaxStamina = function() { - const effAgility = Player.agility * this.skillMultipliers.effAgi; - let maxStamina = (Math.pow(effAgility, 0.8) + this.staminaBonus) * - this.skillMultipliers.stamina * - Player.bladeburner_max_stamina_mult; - if (this.maxStamina !== maxStamina) { - const oldMax = this.maxStamina; - this.maxStamina = maxStamina; - this.stamina = this.maxStamina * this.stamina / oldMax; - } - if (isNaN(maxStamina)) {throw new Error("Max Stamina calculated to be NaN in Bladeburner.calculateMaxStamina()");} -} - -Bladeburner.prototype.calculateStaminaGainPerSecond = function() { - var effAgility = Player.agility * this.skillMultipliers.effAgi; - var maxStaminaBonus = this.maxStamina / BladeburnerConstants.MaxStaminaToGainFactor; - var gain = (BladeburnerConstants.StaminaGainPerSecond + maxStaminaBonus) * Math.pow(effAgility, 0.17); - return gain * (this.skillMultipliers.stamina * Player.bladeburner_stamina_gain_mult); -} - -Bladeburner.prototype.calculateStaminaPenalty = function() { - return Math.min(1, this.stamina / (0.5 * this.maxStamina)); -} - -Bladeburner.prototype.getCurrentCity = function() { - var city = this.cities[this.city]; - if (!(city instanceof City)) { - throw new Error("Bladeburner.getCurrentCity() did not properly return a City object"); - } - return city; -} - -Bladeburner.prototype.upgradeSkill = function(skill) { - // This does NOT handle deduction of skill points - var skillName = skill.name; - if (this.skills[skillName]) { - ++this.skills[skillName]; - } else { - this.skills[skillName] = 1; - } - if (isNaN(this.skills[skillName]) || this.skills[skillName] < 0) { - throw new Error("Level of Skill " + skillName + " is invalid: " + this.skills[skillName]); - } - updateSkillMultipliers(this); -} - -// Sets the player to the "IDLE" action -Bladeburner.prototype.resetAction = function() { - this.action = new ActionIdentifier({type:ActionTypes.Idle}); -} - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////// Unconvertable for now ///////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - +Bladeburner.prototype.prestige = function() { prestige(this); } +Bladeburner.prototype.storeCycles = function(numCycles=1) { storeCycles(this, numCycles); } +Bladeburner.prototype.calculateStaminaPenalty = function() { return calculateStaminaPenalty(this); } +Bladeburner.prototype.getCurrentCity = function() { return getCurrentCity(this); } +Bladeburner.prototype.upgradeSkill = function(skill) { upgradeSkill(this, skill); } // Bladeburner Console Window -Bladeburner.prototype.postToConsole = function(input, saveToLogs=true) { - const MaxConsoleEntries = 100; - if (saveToLogs) { - this.consoleLogs.push(input); - if (this.consoleLogs.length > MaxConsoleEntries) { - this.consoleLogs.shift(); - } - } -} - -Bladeburner.prototype.log = function(input) { - // Adds a timestamp and then just calls postToConsole - this.postToConsole(`[${getTimestamp()}] ${input}`); -} - +Bladeburner.prototype.postToConsole = function(input, saveToLogs=true) { postToConsole(this, input, saveToLogs); } +Bladeburner.prototype.log = function(input) { log(this, input); } // Handles a potential series of commands (comm1; comm2; comm3;) -Bladeburner.prototype.executeConsoleCommands = function(commands) { - executeConsoleCommands(this, commands); -} +Bladeburner.prototype.executeConsoleCommands = function(commands) { executeConsoleCommands(this, Player, commands); } //////////////////////////////////////////////////////////////////////////////// //////////////////////////////// Netscript Fns ///////////////////////////////// //////////////////////////////////////////////////////////////////////////////// -Bladeburner.prototype.getTypeAndNameFromActionId = function(actionId) { - var res = {}; - let types = Object.keys(ActionTypes); - for (let i = 0; i < types.length; ++i) { - if (actionId.type === ActionTypes[types[i]]) { - res.type = types[i]; - break; - } - } - if (res.type == null) {res.type = "Idle";} - - res.name = actionId.name != null ? actionId.name : "Idle"; - return res; -} - -Bladeburner.prototype.getContractNamesNetscriptFn = function() { - return Object.keys(this.contracts); -} - -Bladeburner.prototype.getOperationNamesNetscriptFn = function() { - return Object.keys(this.operations); -} - -Bladeburner.prototype.getBlackOpNamesNetscriptFn = function() { - return Object.keys(BlackOperations); -} - -Bladeburner.prototype.getGeneralActionNamesNetscriptFn = function() { - return Object.keys(GeneralActions); -} - -Bladeburner.prototype.getSkillNamesNetscriptFn = function() { - return Object.keys(Skills); -} - -Bladeburner.prototype.startActionNetscriptFn = function(type, name, workerScript) { - const errorLogText = `Invalid action: type='${type}' name='${name}'`; - const actionId = getActionIdFromTypeAndName(this, type, name); - if (actionId == null) { - workerScript.log("bladeburner.startAction", errorLogText); - return false; - } - - // Special logic for Black Ops - if (actionId.type === ActionTypes["BlackOp"]) { - // Can't start a BlackOp if you don't have the required rank - let action = getActionObject(this, actionId); - if (action.reqdRank > this.rank) { - workerScript.log("bladeburner.startAction", `Insufficient rank to start Black Op '${actionId.name}'.`); - return false; - } - - // Can't start a BlackOp if its already been done - if (this.blackops[actionId.name] != null) { - workerScript.log("bladeburner.startAction", `Black Op ${actionId.name} has already been completed.`); - return false; - } - - // Can't start a BlackOp if you haven't done the one before it - var blackops = []; - for (const nm in BlackOperations) { - if (BlackOperations.hasOwnProperty(nm)) { - blackops.push(nm); - } - } - blackops.sort(function(a, b) { - return (BlackOperations[a].reqdRank - BlackOperations[b].reqdRank); // Sort black ops in intended order - }); - - let i = blackops.indexOf(actionId.name); - if (i === -1) { - workerScript.log("bladeburner.startAction", `Invalid Black Op: '${name}'`); - return false; - } - - if (i > 0 && this.blackops[blackops[i-1]] == null) { - workerScript.log("bladeburner.startAction", `Preceding Black Op must be completed before starting '${actionId.name}'.`); - return false; - } - } - - try { - startAction(this, Player, actionId); - workerScript.log("bladeburner.startAction", `Starting bladeburner action with type '${type}' and name ${name}"`); - return true; - } catch(e) { - resetAction(this); - workerScript.log("bladeburner.startAction", errorLogText); - return false; - } -} - -Bladeburner.prototype.getActionTimeNetscriptFn = function(type, name, workerScript) { - const errorLogText = `Invalid action: type='${type}' name='${name}'` - const actionId = getActionIdFromTypeAndName(this, type, name); - if (actionId == null) { - workerScript.log("bladeburner.getActionTime", errorLogText); - return -1; - } - - const actionObj = getActionObject(this, actionId); - if (actionObj == null) { - workerScript.log("bladeburner.getActionTime", errorLogText); - return -1; - } - - switch (actionId.type) { - case ActionTypes["Contract"]: - case ActionTypes["Operation"]: - case ActionTypes["BlackOp"]: - case ActionTypes["BlackOperation"]: - return actionObj.getActionTime(this); - case ActionTypes["Training"]: - case ActionTypes["Field Analysis"]: - case ActionTypes["FieldAnalysis"]: - return 30; - case ActionTypes["Recruitment"]: - return getRecruitmentTime(this, Player); - case ActionTypes["Diplomacy"]: - case ActionTypes["Hyperbolic Regeneration Chamber"]: - return 60; - default: - workerScript.log("bladeburner.getActionTime", errorLogText); - return -1; - } -} - -Bladeburner.prototype.getActionEstimatedSuccessChanceNetscriptFn = function(type, name, workerScript) { - const errorLogText = `Invalid action: type='${type}' name='${name}'` - const actionId = getActionIdFromTypeAndName(this, type, name); - if (actionId == null) { - workerScript.log("bladeburner.getActionEstimatedSuccessChance", errorLogText); - return -1; - } - - const actionObj = getActionObject(this, actionId); - if (actionObj == null) { - workerScript.log("bladeburner.getActionEstimatedSuccessChance", errorLogText); - return -1; - } - - switch (actionId.type) { - case ActionTypes["Contract"]: - case ActionTypes["Operation"]: - case ActionTypes["BlackOp"]: - case ActionTypes["BlackOperation"]: - return actionObj.getSuccessChance(this, {est:true}); - case ActionTypes["Training"]: - case ActionTypes["Field Analysis"]: - case ActionTypes["FieldAnalysis"]: - return 1; - case ActionTypes["Recruitment"]: - return getRecruitmentSuccessChance(this, Player); - default: - workerScript.log("bladeburner.getActionEstimatedSuccessChance", errorLogText); - return -1; - } -} - -Bladeburner.prototype.getActionCountRemainingNetscriptFn = function(type, name, workerScript) { - const errorLogText = `Invalid action: type='${type}' name='${name}'`; - const actionId = getActionIdFromTypeAndName(this, type, name); - if (actionId == null) { - workerScript.log("bladeburner.getActionCountRemaining", errorLogText); - return -1; - } - - const actionObj = getActionObject(this, actionId); - if (actionObj == null) { - workerScript.log("bladeburner.getActionCountRemaining", errorLogText); - return -1; - } - - switch (actionId.type) { - case ActionTypes["Contract"]: - case ActionTypes["Operation"]: - return Math.floor( actionObj.count ); - case ActionTypes["BlackOp"]: - case ActionTypes["BlackOperation"]: - if (this.blackops[name] != null) { - return 0; - } else { - return 1; - } - case ActionTypes["Training"]: - case ActionTypes["Field Analysis"]: - case ActionTypes["FieldAnalysis"]: - return Infinity; - default: - workerScript.log("bladeburner.getActionCountRemaining", errorLogText); - return -1; - } -} - -Bladeburner.prototype.getSkillLevelNetscriptFn = function(skillName, workerScript) { - if (skillName === "" || !Skills.hasOwnProperty(skillName)) { - workerScript.log("bladeburner.getSkillLevel", `Invalid skill: '${skillName}'`); - return -1; - } - - if (this.skills[skillName] == null) { - return 0; - } else { - return this.skills[skillName]; - } -} - -Bladeburner.prototype.getSkillUpgradeCostNetscriptFn = function(skillName, workerScript) { - if (skillName === "" || !Skills.hasOwnProperty(skillName)) { - workerScript.log("bladeburner.getSkillUpgradeCost", `Invalid skill: '${skillName}'`); - return -1; - } - - const skill = Skills[skillName]; - if (this.skills[skillName] == null) { - return skill.calculateCost(0); - } else { - return skill.calculateCost(this.skills[skillName]); - } -} - -Bladeburner.prototype.upgradeSkillNetscriptFn = function(skillName, workerScript) { - const errorLogText = `Invalid skill: '${skillName}'`; - if (!Skills.hasOwnProperty(skillName)) { - workerScript.log("bladeburner.upgradeSkill", errorLogText); - return false; - } - - const skill = Skills[skillName]; - let currentLevel = 0; - if (this.skills[skillName] && !isNaN(this.skills[skillName])) { - currentLevel = this.skills[skillName]; - } - const cost = skill.calculateCost(currentLevel); - - if(skill.maxLvl && currentLevel >= skill.maxLvl) { - workerScript.log("bladeburner.upgradeSkill", `Skill '${skillName}' is already maxed.`); - return false; - } - - if (this.skillPoints < cost) { - workerScript.log("bladeburner.upgradeSkill", `You do not have enough skill points to upgrade ${skillName} (You have ${this.skillPoints}, you need ${cost})`); - return false; - } - - this.skillPoints -= cost; - this.upgradeSkill(skill); - workerScript.log("bladeburner.upgradeSkill", `'${skillName}' upgraded to level ${this.skills[skillName]}`); - return true; -} - -Bladeburner.prototype.getTeamSizeNetscriptFn = function(type, name, workerScript) { - if (type === "" && name === "") { - return this.teamSize; - } - - const errorLogText = `Invalid action: type='${type}' name='${name}'`; - const actionId = getActionIdFromTypeAndName(this, type, name); - if (actionId == null) { - workerScript.log("bladeburner.getTeamSize", errorLogText); - return -1; - } - - const actionObj = getActionObject(this, actionId); - if (actionObj == null) { - workerScript.log("bladeburner.getTeamSize", errorLogText); - return -1; - } - - if (actionId.type === ActionTypes["Operation"] || - actionId.type === ActionTypes["BlackOp"] || - actionId.type === ActionTypes["BlackOperation"]) { - return actionObj.teamCount; - } else { - return 0; - } -} - -Bladeburner.prototype.setTeamSizeNetscriptFn = function(type, name, size, workerScript) { - const errorLogText = `Invalid action: type='${type}' name='${name}'`; - const actionId = getActionIdFromTypeAndName(this, type, name); - if (actionId == null) { - workerScript.log("bladeburner.setTeamSize", errorLogText); - return -1; - } - - if (actionId.type !== ActionTypes["Operation"] && - actionId.type !== ActionTypes["BlackOp"] && - actionId.type !== ActionTypes["BlackOperation"]) { - workerScript.log("bladeburner.setTeamSize", "Only valid for 'Operations' and 'BlackOps'"); - return -1; - } - - const actionObj = getActionObject(this, actionId); - if (actionObj == null) { - workerScript.log("bladeburner.setTeamSize", errorLogText); - return -1; - } - - let sanitizedSize = Math.round(size); - if (isNaN(sanitizedSize) || sanitizedSize < 0) { - workerScript.log("bladeburner.setTeamSize", `Invalid size: ${size}`); - return -1; - } - if (this.teamSize < sanitizedSize) {sanitizedSize = this.teamSize;} - actionObj.teamCount = sanitizedSize; - workerScript.log("bladeburner.setTeamSize", `Team size for '${name}' set to ${sanitizedSize}.`); - return sanitizedSize; -} - -Bladeburner.prototype.joinBladeburnerFactionNetscriptFn = function(workerScript) { - var bladeburnerFac = Factions["Bladeburners"]; - if (bladeburnerFac.isMember) { - return true; - } else if (this.rank >= BladeburnerConstants.RankNeededForFaction) { - joinFaction(bladeburnerFac); - workerScript.log("bladeburner.joinBladeburnerFaction", "Joined Bladeburners faction."); - return true; - } else { - workerScript.log("bladeburner.joinBladeburnerFaction", `You do not have the required rank (${this.rank}/${BladeburnerConstants.RankNeededForFaction}).`); - return false; - } -} +Bladeburner.prototype.getTypeAndNameFromActionId = function(actionId) { getTypeAndNameFromActionId(this, actionId); } +Bladeburner.prototype.getContractNamesNetscriptFn = function() { getContractNamesNetscriptFn(this); } +Bladeburner.prototype.getOperationNamesNetscriptFn = function() { getOperationNamesNetscriptFn(this); } +Bladeburner.prototype.getBlackOpNamesNetscriptFn = function() { getBlackOpNamesNetscriptFn(this); } +Bladeburner.prototype.getGeneralActionNamesNetscriptFn = function() { getGeneralActionNamesNetscriptFn(this); } +Bladeburner.prototype.getSkillNamesNetscriptFn = function() { getSkillNamesNetscriptFn(this); } +Bladeburner.prototype.startActionNetscriptFn = function(type, name, workerScript) { startActionNetscriptFn(this, Player, type, name, workerScript); } +Bladeburner.prototype.getActionTimeNetscriptFn = function(type, name, workerScript) { getActionTimeNetscriptFn(this, Player, type, name, workerScript); } +Bladeburner.prototype.getActionEstimatedSuccessChanceNetscriptFn = function(type, name, workerScript) { getActionEstimatedSuccessChanceNetscriptFn(this, Player, type, name, workerScript); } +Bladeburner.prototype.getActionCountRemainingNetscriptFn = function(type, name, workerScript) { getActionCountRemainingNetscriptFn(this, Player, type, name, workerScript); } +Bladeburner.prototype.getSkillLevelNetscriptFn = function(skillName, workerScript) { getSkillLevelNetscriptFn(this, skillName, workerScript); } +Bladeburner.prototype.getSkillUpgradeCostNetscriptFn = function(skillName, workerScript) { getSkillUpgradeCostNetscriptFn(this, skillName, workerScript); } +Bladeburner.prototype.upgradeSkillNetscriptFn = function(skillName, workerScript) { upgradeSkillNetscriptFn(this, skillName, workerScript); } +Bladeburner.prototype.getTeamSizeNetscriptFn = function(type, name, workerScript) { getTeamSizeNetscriptFn(this, type, name, workerScript); } +Bladeburner.prototype.setTeamSizeNetscriptFn = function(type, name, size, workerScript) { setTeamSizeNetscriptFn(this, type, name, size, workerScript); } +Bladeburner.prototype.joinBladeburnerFactionNetscriptFn = function(workerScript) { joinBladeburnerFactionNetscriptFn(this, workerScript); } Bladeburner.prototype.toJSON = function() { return Generic_toJSON("Bladeburner", this); diff --git a/src/Bladeburner/Bladeburner.ts b/src/Bladeburner/Bladeburner.ts index 067f76949..f5debedd0 100644 --- a/src/Bladeburner/Bladeburner.ts +++ b/src/Bladeburner/Bladeburner.ts @@ -8,6 +8,8 @@ import { ActionIdentifier } from "./ActionIdentifier"; import { ActionTypes } from "./data/ActionTypes"; import { BlackOperations } from "./BlackOperations"; import { BlackOperation } from "./BlackOperation"; +import { Operation } from "./Operation"; +import { Contract } from "./Contract"; import { GeneralActions } from "./GeneralActions"; import { formatNumber } from "../../utils/StringHelperFunctions"; import { Skills } from "./Skills"; @@ -25,7 +27,14 @@ import { addOffset } from "../../utils/helpers/addOffset"; import { Faction } from "../Faction/Faction"; import { Factions, factionExists } from "../Faction/Factions"; import { calculateHospitalizationCost } from "../Hospital/Hospital"; -import { hackWorldDaemon } from "../RedPill"; +import { hackWorldDaemon, redPillFlag } from "../RedPill"; +import { dialogBoxCreate } from "../../utils/DialogBox"; +import { Settings } from "../Settings/Settings"; +import { Augmentations } from "../Augmentation/Augmentations"; +import { AugmentationNames } from "../Augmentation/data/AugmentationNames"; +import { getTimestamp } from "../../utils/helpers/getTimestamp"; +import { joinFaction } from "../Faction/FactionHelpers"; +import { WorkerScript } from "../Netscript/WorkerScript"; export function getActionIdFromTypeAndName(bladeburner: IBladeburner, type: string = "", name: string = ""): IActionIdentifier | null { if (type === "" || name === "") {return null;} @@ -548,7 +557,7 @@ export function executeConsoleCommand(bladeburner: IBladeburner, player: IPlayer executeStartConsoleCommand(bladeburner, player, args); break; case "stop": - bladeburner.resetAction(); + resetAction(bladeburner); break; default: bladeburner.postToConsole("Invalid console command"); @@ -1310,4 +1319,597 @@ export function startAction(bladeburner: IBladeburner, player: IPlayer, actionId throw new Error("Invalid Action Type in startAction(Bladeburner,player, ): " + actionId.type); break; } -} \ No newline at end of file +} + +export function calculateStaminaPenalty(bladeburner: IBladeburner): number { + return Math.min(1, bladeburner.stamina / (0.5 * bladeburner.maxStamina)); +} + +export function calculateStaminaGainPerSecond(bladeburner: IBladeburner, player: IPlayer): number { + const effAgility = player.agility * bladeburner.skillMultipliers.effAgi; + const maxStaminaBonus = bladeburner.maxStamina / BladeburnerConstants.MaxStaminaToGainFactor; + const gain = (BladeburnerConstants.StaminaGainPerSecond + maxStaminaBonus) * Math.pow(effAgility, 0.17); + return gain * (bladeburner.skillMultipliers.stamina * player.bladeburner_stamina_gain_mult); +} + +export function calculateMaxStamina(bladeburner: IBladeburner, player: IPlayer) { + const effAgility = player.agility * bladeburner.skillMultipliers.effAgi; + let maxStamina = (Math.pow(effAgility, 0.8) + bladeburner.staminaBonus) * + bladeburner.skillMultipliers.stamina * + player.bladeburner_max_stamina_mult; + if (bladeburner.maxStamina !== maxStamina) { + const oldMax = bladeburner.maxStamina; + bladeburner.maxStamina = maxStamina; + bladeburner.stamina = bladeburner.maxStamina * bladeburner.stamina / oldMax; + } + if (isNaN(maxStamina)) {throw new Error("Max Stamina calculated to be NaN in Bladeburner.calculateMaxStamina()");} +} + +export function create(bladeburner: IBladeburner): void { + bladeburner.contracts["Tracking"] = new Contract({ + name:"Tracking", + desc:"Identify and locate Synthoids. This contract involves reconnaissance " + + "and information-gathering ONLY. Do NOT engage. Stealth is of the utmost importance.

        " + + "Successfully completing Tracking contracts will slightly improve your Synthoid population estimate for " + + "whatever city you are currently in.", + baseDifficulty:125,difficultyFac:1.02,rewardFac:1.041, + rankGain:0.3, hpLoss:0.5, + count:getRandomInt(25, 150), countGrowth:getRandomInt(5, 75)/10, + weights:{hack:0,str:0.05,def:0.05,dex:0.35,agi:0.35,cha:0.1, int:0.05}, + decays:{hack:0,str:0.91,def:0.91,dex:0.91,agi:0.91,cha:0.9, int:1}, + isStealth:true, + }); + bladeburner.contracts["Bounty Hunter"] = new Contract({ + name:"Bounty Hunter", + desc:"Hunt down and capture fugitive Synthoids. These Synthoids are wanted alive.

        " + + "Successfully completing a Bounty Hunter contract will lower the population in your " + + "current city, and will also increase its chaos level.", + baseDifficulty:250, difficultyFac:1.04,rewardFac:1.085, + rankGain:0.9, hpLoss:1, + count:getRandomInt(5, 150), countGrowth:getRandomInt(5, 75)/10, + weights:{hack:0,str:0.15,def:0.15,dex:0.25,agi:0.25,cha:0.1, int:0.1}, + decays:{hack:0,str:0.91,def:0.91,dex:0.91,agi:0.91,cha:0.8, int:0.9}, + isKill:true, + }); + bladeburner.contracts["Retirement"] = new Contract({ + name:"Retirement", + desc:"Hunt down and retire (kill) rogue Synthoids.

        " + + "Successfully completing a Retirement contract will lower the population in your current " + + "city, and will also increase its chaos level.", + baseDifficulty:200, difficultyFac:1.03, rewardFac:1.065, + rankGain:0.6, hpLoss:1, + count:getRandomInt(5, 150), countGrowth:getRandomInt(5, 75)/10, + weights:{hack:0,str:0.2,def:0.2,dex:0.2,agi:0.2,cha:0.1, int:0.1}, + decays:{hack:0,str:0.91,def:0.91,dex:0.91,agi:0.91,cha:0.8, int:0.9}, + isKill:true, + }); + + bladeburner.operations["Investigation"] = new Operation({ + name:"Investigation", + desc:"As a field agent, investigate and identify Synthoid " + + "populations, movements, and operations.

        Successful " + + "Investigation ops will increase the accuracy of your " + + "synthoid data.

        " + + "You will NOT lose HP from failed Investigation ops.", + baseDifficulty:400, difficultyFac:1.03,rewardFac:1.07,reqdRank:25, + rankGain:2.2, rankLoss:0.2, + count:getRandomInt(1, 100), countGrowth:getRandomInt(10, 40)/10, + weights:{hack:0.25,str:0.05,def:0.05,dex:0.2,agi:0.1,cha:0.25, int:0.1}, + decays:{hack:0.85,str:0.9,def:0.9,dex:0.9,agi:0.9,cha:0.7, int:0.9}, + isStealth:true, + }); + bladeburner.operations["Undercover Operation"] = new Operation({ + name:"Undercover Operation", + desc:"Conduct undercover operations to identify hidden " + + "and underground Synthoid communities and organizations.

        " + + "Successful Undercover ops will increase the accuracy of your synthoid " + + "data.", + baseDifficulty:500, difficultyFac:1.04, rewardFac:1.09, reqdRank:100, + rankGain:4.4, rankLoss:0.4, hpLoss:2, + count:getRandomInt(1, 100), countGrowth:getRandomInt(10, 40)/10, + weights:{hack:0.2,str:0.05,def:0.05,dex:0.2,agi:0.2,cha:0.2, int:0.1}, + decays:{hack:0.8,str:0.9,def:0.9,dex:0.9,agi:0.9,cha:0.7, int:0.9}, + isStealth:true, + }); + bladeburner.operations["Sting Operation"] = new Operation({ + name:"Sting Operation", + desc:"Conduct a sting operation to bait and capture particularly " + + "notorious Synthoid criminals.", + baseDifficulty:650, difficultyFac:1.04, rewardFac:1.095, reqdRank:500, + rankGain:5.5, rankLoss:0.5, hpLoss:2.5, + count:getRandomInt(1, 150), countGrowth:getRandomInt(3, 40)/10, + weights:{hack:0.25,str:0.05,def:0.05,dex:0.25,agi:0.1,cha:0.2, int:0.1}, + decays:{hack:0.8,str:0.85,def:0.85,dex:0.85,agi:0.85,cha:0.7, int:0.9}, + isStealth:true, + }); + bladeburner.operations["Raid"] = new Operation({ + name:"Raid", + desc:"Lead an assault on a known Synthoid community. Note that " + + "there must be an existing Synthoid community in your current city " + + "in order for this Operation to be successful.", + baseDifficulty:800, difficultyFac:1.045, rewardFac:1.1, reqdRank:3000, + rankGain:55,rankLoss:2.5,hpLoss:50, + count:getRandomInt(1, 150), countGrowth:getRandomInt(2, 40)/10, + weights:{hack:0.1,str:0.2,def:0.2,dex:0.2,agi:0.2,cha:0, int:0.1}, + decays:{hack:0.7,str:0.8,def:0.8,dex:0.8,agi:0.8,cha:0, int:0.9}, + isKill:true, + }); + bladeburner.operations["Stealth Retirement Operation"] = new Operation({ + name:"Stealth Retirement Operation", + desc:"Lead a covert operation to retire Synthoids. The " + + "objective is to complete the task without " + + "drawing any attention. Stealth and discretion are key.", + baseDifficulty:1000, difficultyFac:1.05, rewardFac:1.11, reqdRank:20e3, + rankGain:22, rankLoss:2, hpLoss:10, + count:getRandomInt(1, 150), countGrowth:getRandomInt(1, 20)/10, + weights:{hack:0.1,str:0.1,def:0.1,dex:0.3,agi:0.3,cha:0, int:0.1}, + decays:{hack:0.7,str:0.8,def:0.8,dex:0.8,agi:0.8,cha:0, int:0.9}, + isStealth:true, isKill:true, + }); + bladeburner.operations["Assassination"] = new Operation({ + name:"Assassination", + desc:"Assassinate Synthoids that have been identified as " + + "important, high-profile social and political leaders " + + "in the Synthoid communities.", + baseDifficulty:1500, difficultyFac:1.06, rewardFac:1.14, reqdRank:50e3, + rankGain:44, rankLoss:4, hpLoss:5, + count:getRandomInt(1, 150), countGrowth:getRandomInt(1, 20)/10, + weights:{hack:0.1,str:0.1,def:0.1,dex:0.3,agi:0.3,cha:0, int:0.1}, + decays:{hack:0.6,str:0.8,def:0.8,dex:0.8,agi:0.8,cha:0, int:0.8}, + isStealth:true, isKill:true, + }); +} + +export function process(bladeburner: IBladeburner, player: IPlayer): void { + // Edge case condition...if Operation Daedalus is complete trigger the BitNode + if (redPillFlag === false && bladeburner.blackops.hasOwnProperty("Operation Daedalus")) { + return hackWorldDaemon(player.bitNodeN); + } + + // If the Player starts doing some other actions, set action to idle and alert + if (Augmentations[AugmentationNames.BladesSimulacrum].owned === false && player.isWorking) { + if (bladeburner.action.type !== ActionTypes["Idle"]) { + let msg = "Your Bladeburner action was cancelled because you started doing something else."; + if (bladeburner.automateEnabled) { + msg += `

        Your automation was disabled as well. You will have to re-enable it through the Bladeburner console` + bladeburner.automateEnabled = false; + } + if (!Settings.SuppressBladeburnerPopup) { + dialogBoxCreate(msg); + } + } + resetAction(bladeburner); + } + + // If the Player has no Stamina, set action to idle + if (bladeburner.stamina <= 0) { + bladeburner.log("Your Bladeburner action was cancelled because your stamina hit 0"); + resetAction(bladeburner); + } + + // A 'tick' for this mechanic is one second (= 5 game cycles) + if (bladeburner.storedCycles >= BladeburnerConstants.CyclesPerSecond) { + let seconds = Math.floor(bladeburner.storedCycles / BladeburnerConstants.CyclesPerSecond); + seconds = Math.min(seconds, 5); // Max of 5 'ticks' + bladeburner.storedCycles -= seconds * BladeburnerConstants.CyclesPerSecond; + + // Stamina + calculateMaxStamina(bladeburner, player); + bladeburner.stamina += (calculateStaminaGainPerSecond(bladeburner, player) * seconds); + bladeburner.stamina = Math.min(bladeburner.maxStamina, bladeburner.stamina); + + // Count increase for contracts/operations + for (const contract of (Object.values(bladeburner.contracts) as Contract[])) { + contract.count += (seconds * contract.countGrowth/BladeburnerConstants.ActionCountGrowthPeriod); + } + for (const op of (Object.values(bladeburner.operations) as Operation[])) { + op.count += (seconds * op.countGrowth/BladeburnerConstants.ActionCountGrowthPeriod); + } + + // Chaos goes down very slowly + for (const cityName of BladeburnerConstants.CityNames) { + const city = bladeburner.cities[cityName]; + if (!(city instanceof City)) {throw new Error("Invalid City object when processing passive chaos reduction in Bladeburner.process");} + city.chaos -= (0.0001 * seconds); + city.chaos = Math.max(0, city.chaos); + } + + // Random Events + bladeburner.randomEventCounter -= seconds; + if (bladeburner.randomEventCounter <= 0) { + randomEvent(bladeburner); + // Add instead of setting because we might have gone over the required time for the event + bladeburner.randomEventCounter += getRandomInt(240, 600); + } + + processAction(bladeburner, player, seconds); + + // Automation + if (bladeburner.automateEnabled) { + // Note: Do NOT set bladeburner.action = bladeburner.automateActionHigh/Low since it creates a reference + if (bladeburner.stamina <= bladeburner.automateThreshLow) { + if (bladeburner.action.name !== bladeburner.automateActionLow.name || bladeburner.action.type !== bladeburner.automateActionLow.type) { + bladeburner.action = new ActionIdentifier({type: bladeburner.automateActionLow.type, name: bladeburner.automateActionLow.name}); + startAction(bladeburner, player, bladeburner.action); + } + } else if (bladeburner.stamina >= bladeburner.automateThreshHigh) { + if (bladeburner.action.name !== bladeburner.automateActionHigh.name || bladeburner.action.type !== bladeburner.automateActionHigh.type) { + bladeburner.action = new ActionIdentifier({type: bladeburner.automateActionHigh.type, name: bladeburner.automateActionHigh.name}); + startAction(bladeburner, player, bladeburner.action); + } + } + } + } +} + + + + + + + + +export function prestige(bladeburner: IBladeburner): void { + resetAction(bladeburner); + const bladeburnerFac = Factions["Bladeburners"]; + if (bladeburner.rank >= BladeburnerConstants.RankNeededForFaction) { + joinFaction(bladeburnerFac); + } +} + +export function storeCycles(bladeburner: IBladeburner, numCycles: number = 1): void { + bladeburner.storedCycles += numCycles; +} + +export function getCurrentCity(bladeburner: IBladeburner): City { + const city = bladeburner.cities[bladeburner.city]; + if (!(city instanceof City)) { + throw new Error("Bladeburner.getCurrentCity() did not properly return a City object"); + } + return city; +} + +export function upgradeSkill(bladeburner: IBladeburner, skill: Skill) { + // This does NOT handle deduction of skill points + const skillName = skill.name; + if (bladeburner.skills[skillName]) { + ++bladeburner.skills[skillName]; + } else { + bladeburner.skills[skillName] = 1; + } + if (isNaN(bladeburner.skills[skillName]) || bladeburner.skills[skillName] < 0) { + throw new Error("Level of Skill " + skillName + " is invalid: " + bladeburner.skills[skillName]); + } + updateSkillMultipliers(bladeburner); +} + +// Bladeburner Console Window +export function postToConsole(bladeburner: IBladeburner, input: string, saveToLogs: boolean = true) { + const MaxConsoleEntries = 100; + if (saveToLogs) { + bladeburner.consoleLogs.push(input); + if (bladeburner.consoleLogs.length > MaxConsoleEntries) { + bladeburner.consoleLogs.shift(); + } + } +} + +export function log(bladeburner: IBladeburner, input: string) { + // Adds a timestamp and then just calls postToConsole + bladeburner.postToConsole(`[${getTimestamp()}] ${input}`); +} + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////// Netscript Fns ///////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +export function getTypeAndNameFromActionId(bladeburner: IBladeburner, actionId: IActionIdentifier) { + const res = {type: '', name: ''}; + const types = Object.keys(ActionTypes); + for (let i = 0; i < types.length; ++i) { + if (actionId.type === ActionTypes[types[i]]) { + res.type = types[i]; + break; + } + } + if (res.type == null) {res.type = "Idle";} + + res.name = actionId.name != null ? actionId.name : "Idle"; + return res; +} +export function getContractNamesNetscriptFn(bladeburner: IBladeburner) { + return Object.keys(bladeburner.contracts); +} +export function getOperationNamesNetscriptFn(bladeburner: IBladeburner) { + return Object.keys(bladeburner.operations); +} +export function getBlackOpNamesNetscriptFn(bladeburner: IBladeburner) { + return Object.keys(BlackOperations); +} +export function getGeneralActionNamesNetscriptFn(bladeburner: IBladeburner) { + return Object.keys(GeneralActions); +} +export function getSkillNamesNetscriptFn(bladeburner: IBladeburner) { + return Object.keys(Skills); +} +export function startActionNetscriptFn(bladeburner: IBladeburner, player: IPlayer, type: string, name: string, workerScript: WorkerScript) { + const errorLogText = `Invalid action: type='${type}' name='${name}'`; + const actionId = getActionIdFromTypeAndName(bladeburner, type, name); + if (actionId == null) { + workerScript.log("bladeburner.startAction", errorLogText); + return false; + } + + // Special logic for Black Ops + if (actionId.type === ActionTypes["BlackOp"]) { + // Can't start a BlackOp if you don't have the required rank + const action = getActionObject(bladeburner, actionId); + if(action == null) throw new Error('Action not found ${actionId.type}, ${actionId.name}'); + if(!(action instanceof BlackOperation)) throw new Error(`Action should be BlackOperation but isn't`); + const blackOp = (action as BlackOperation); + if (action.reqdRank > bladeburner.rank) { + workerScript.log("bladeburner.startAction", `Insufficient rank to start Black Op '${actionId.name}'.`); + return false; + } + + // Can't start a BlackOp if its already been done + if (bladeburner.blackops[actionId.name] != null) { + workerScript.log("bladeburner.startAction", `Black Op ${actionId.name} has already been completed.`); + return false; + } + + // Can't start a BlackOp if you haven't done the one before it + var blackops = []; + for (const nm in BlackOperations) { + if (BlackOperations.hasOwnProperty(nm)) { + blackops.push(nm); + } + } + blackops.sort(function(a, b) { + return (BlackOperations[a].reqdRank - BlackOperations[b].reqdRank); // Sort black ops in intended order + }); + + let i = blackops.indexOf(actionId.name); + if (i === -1) { + workerScript.log("bladeburner.startAction", `Invalid Black Op: '${name}'`); + return false; + } + + if (i > 0 && bladeburner.blackops[blackops[i-1]] == null) { + workerScript.log("bladeburner.startAction", `Preceding Black Op must be completed before starting '${actionId.name}'.`); + return false; + } + } + + try { + startAction(bladeburner, player, actionId); + workerScript.log("bladeburner.startAction", `Starting bladeburner action with type '${type}' and name ${name}"`); + return true; + } catch(e) { + resetAction(bladeburner); + workerScript.log("bladeburner.startAction", errorLogText); + return false; + } +} +export function getActionTimeNetscriptFn(bladeburner: IBladeburner, player: IPlayer, type: string, name: string, workerScript: WorkerScript) { + const errorLogText = `Invalid action: type='${type}' name='${name}'` + const actionId = getActionIdFromTypeAndName(bladeburner, type, name); + if (actionId == null) { + workerScript.log("bladeburner.getActionTime", errorLogText); + return -1; + } + + const actionObj = getActionObject(bladeburner, actionId); + if (actionObj == null) { + workerScript.log("bladeburner.getActionTime", errorLogText); + return -1; + } + + switch (actionId.type) { + case ActionTypes["Contract"]: + case ActionTypes["Operation"]: + case ActionTypes["BlackOp"]: + case ActionTypes["BlackOperation"]: + return actionObj.getActionTime(bladeburner); + case ActionTypes["Training"]: + case ActionTypes["Field Analysis"]: + case ActionTypes["FieldAnalysis"]: + return 30; + case ActionTypes["Recruitment"]: + return getRecruitmentTime(bladeburner, player); + case ActionTypes["Diplomacy"]: + case ActionTypes["Hyperbolic Regeneration Chamber"]: + return 60; + default: + workerScript.log("bladeburner.getActionTime", errorLogText); + return -1; + } +} +export function getActionEstimatedSuccessChanceNetscriptFn(bladeburner: IBladeburner, player: IPlayer, type: string, name: string, workerScript: WorkerScript) { + const errorLogText = `Invalid action: type='${type}' name='${name}'` + const actionId = getActionIdFromTypeAndName(bladeburner, type, name); + if (actionId == null) { + workerScript.log("bladeburner.getActionEstimatedSuccessChance", errorLogText); + return -1; + } + + const actionObj = getActionObject(bladeburner, actionId); + if (actionObj == null) { + workerScript.log("bladeburner.getActionEstimatedSuccessChance", errorLogText); + return -1; + } + + switch (actionId.type) { + case ActionTypes["Contract"]: + case ActionTypes["Operation"]: + case ActionTypes["BlackOp"]: + case ActionTypes["BlackOperation"]: + return actionObj.getSuccessChance(bladeburner, {est:true}); + case ActionTypes["Training"]: + case ActionTypes["Field Analysis"]: + case ActionTypes["FieldAnalysis"]: + return 1; + case ActionTypes["Recruitment"]: + return getRecruitmentSuccessChance(bladeburner, player); + default: + workerScript.log("bladeburner.getActionEstimatedSuccessChance", errorLogText); + return -1; + } +} +export function getActionCountRemainingNetscriptFn(bladeburner: IBladeburner, type: string, name: string, workerScript: WorkerScript) { + const errorLogText = `Invalid action: type='${type}' name='${name}'`; + const actionId = getActionIdFromTypeAndName(bladeburner, type, name); + if (actionId == null) { + workerScript.log("bladeburner.getActionCountRemaining", errorLogText); + return -1; + } + + const actionObj = getActionObject(bladeburner, actionId); + if (actionObj == null) { + workerScript.log("bladeburner.getActionCountRemaining", errorLogText); + return -1; + } + + switch (actionId.type) { + case ActionTypes["Contract"]: + case ActionTypes["Operation"]: + return Math.floor( actionObj.count ); + case ActionTypes["BlackOp"]: + case ActionTypes["BlackOperation"]: + if (bladeburner.blackops[name] != null) { + return 0; + } else { + return 1; + } + case ActionTypes["Training"]: + case ActionTypes["Field Analysis"]: + case ActionTypes["FieldAnalysis"]: + return Infinity; + default: + workerScript.log("bladeburner.getActionCountRemaining", errorLogText); + return -1; + } +} +export function getSkillLevelNetscriptFn(bladeburner: IBladeburner, skillName: string, workerScript: WorkerScript) { + if (skillName === "" || !Skills.hasOwnProperty(skillName)) { + workerScript.log("bladeburner.getSkillLevel", `Invalid skill: '${skillName}'`); + return -1; + } + + if (bladeburner.skills[skillName] == null) { + return 0; + } else { + return bladeburner.skills[skillName]; + } +} +export function getSkillUpgradeCostNetscriptFn(bladeburner: IBladeburner, skillName: string, workerScript: WorkerScript) { + if (skillName === "" || !Skills.hasOwnProperty(skillName)) { + workerScript.log("bladeburner.getSkillUpgradeCost", `Invalid skill: '${skillName}'`); + return -1; + } + + const skill = Skills[skillName]; + if (bladeburner.skills[skillName] == null) { + return skill.calculateCost(0); + } else { + return skill.calculateCost(bladeburner.skills[skillName]); + } +} +export function upgradeSkillNetscriptFn(bladeburner: IBladeburner, skillName: string, workerScript: WorkerScript) { + const errorLogText = `Invalid skill: '${skillName}'`; + if (!Skills.hasOwnProperty(skillName)) { + workerScript.log("bladeburner.upgradeSkill", errorLogText); + return false; + } + + const skill = Skills[skillName]; + let currentLevel = 0; + if (bladeburner.skills[skillName] && !isNaN(bladeburner.skills[skillName])) { + currentLevel = bladeburner.skills[skillName]; + } + const cost = skill.calculateCost(currentLevel); + + if(skill.maxLvl && currentLevel >= skill.maxLvl) { + workerScript.log("bladeburner.upgradeSkill", `Skill '${skillName}' is already maxed.`); + return false; + } + + if (bladeburner.skillPoints < cost) { + workerScript.log("bladeburner.upgradeSkill", `You do not have enough skill points to upgrade ${skillName} (You have ${bladeburner.skillPoints}, you need ${cost})`); + return false; + } + + bladeburner.skillPoints -= cost; + bladeburner.upgradeSkill(skill); + workerScript.log("bladeburner.upgradeSkill", `'${skillName}' upgraded to level ${bladeburner.skills[skillName]}`); + return true; +} +export function getTeamSizeNetscriptFn(bladeburner: IBladeburner, type: string, name: string, workerScript: WorkerScript): number { + if (type === "" && name === "") { + return bladeburner.teamSize; + } + + const errorLogText = `Invalid action: type='${type}' name='${name}'`; + const actionId = getActionIdFromTypeAndName(bladeburner, type, name); + if (actionId == null) { + workerScript.log("bladeburner.getTeamSize", errorLogText); + return -1; + } + + const actionObj = getActionObject(bladeburner, actionId); + if (actionObj == null) { + workerScript.log("bladeburner.getTeamSize", errorLogText); + return -1; + } + + if (actionId.type === ActionTypes["Operation"] || + actionId.type === ActionTypes["BlackOp"] || + actionId.type === ActionTypes["BlackOperation"]) { + return actionObj.teamCount; + } else { + return 0; + } +} +export function setTeamSizeNetscriptFn(bladeburner: IBladeburner, type: string, name: string, size: number, workerScript: WorkerScript): number { + const errorLogText = `Invalid action: type='${type}' name='${name}'`; + const actionId = getActionIdFromTypeAndName(bladeburner, type, name); + if (actionId == null) { + workerScript.log("bladeburner.setTeamSize", errorLogText); + return -1; + } + + if (actionId.type !== ActionTypes["Operation"] && + actionId.type !== ActionTypes["BlackOp"] && + actionId.type !== ActionTypes["BlackOperation"]) { + workerScript.log("bladeburner.setTeamSize", "Only valid for 'Operations' and 'BlackOps'"); + return -1; + } + + const actionObj = getActionObject(bladeburner, actionId); + if (actionObj == null) { + workerScript.log("bladeburner.setTeamSize", errorLogText); + return -1; + } + + let sanitizedSize = Math.round(size); + if (isNaN(sanitizedSize) || sanitizedSize < 0) { + workerScript.log("bladeburner.setTeamSize", `Invalid size: ${size}`); + return -1; + } + if (bladeburner.teamSize < sanitizedSize) {sanitizedSize = bladeburner.teamSize;} + actionObj.teamCount = sanitizedSize; + workerScript.log("bladeburner.setTeamSize", `Team size for '${name}' set to ${sanitizedSize}.`); + return sanitizedSize; +} +export function joinBladeburnerFactionNetscriptFn(bladeburner: IBladeburner, workerScript: WorkerScript): boolean { + var bladeburnerFac = Factions["Bladeburners"]; + if (bladeburnerFac.isMember) { + return true; + } else if (bladeburner.rank >= BladeburnerConstants.RankNeededForFaction) { + joinFaction(bladeburnerFac); + workerScript.log("bladeburner.joinBladeburnerFaction", "Joined Bladeburners faction."); + return true; + } else { + workerScript.log("bladeburner.joinBladeburnerFaction", `You do not have the required rank (${bladeburner.rank}/${BladeburnerConstants.RankNeededForFaction}).`); + return false; + } +} diff --git a/src/PersonObjects/IPlayer.ts b/src/PersonObjects/IPlayer.ts index 4b40bfaa2..0be00f708 100644 --- a/src/PersonObjects/IPlayer.ts +++ b/src/PersonObjects/IPlayer.ts @@ -43,6 +43,7 @@ export interface IPlayer { homeComputer: string; hp: number; jobs: IMap; + isWorking: boolean; karma: number; location: LocationName; max_hp: number; diff --git a/src/RedPill.d.ts b/src/RedPill.d.ts index 073ed8a6b..5ee741d20 100644 --- a/src/RedPill.d.ts +++ b/src/RedPill.d.ts @@ -1 +1,2 @@ +export declare let redPillFlag: boolean; export declare function hackWorldDaemon(currentNodeNumber: number, flume: boolean = false, quick: boolean = false): void; \ No newline at end of file diff --git a/src/engine.jsx b/src/engine.jsx index dd2f676b7..9ae80b83a 100644 --- a/src/engine.jsx +++ b/src/engine.jsx @@ -18,6 +18,7 @@ import { initBitNodeMultipliers, } from "./BitNode/BitNode"; import { Bladeburner } from "./Bladeburner"; +import { process as ProcessBladeburner } from "./Bladeburner/Bladeburner"; import { CharacterOverviewComponent } from "./ui/React/CharacterOverview"; import { cinematicTextFlag } from "./CinematicText"; import { generateRandomContract } from "./CodingContractGenerator"; @@ -883,7 +884,7 @@ const Engine = { } if (Player.bladeburner instanceof Bladeburner) { try { - Player.bladeburner.process(); + ProcessBladeburner(Player.bladeburner, Player); } catch(e) { exceptionAlert("Exception caught in Bladeburner.process(): " + e); } From 8d550157bc45fb1d120ecf61ab699f8f3045f125 Mon Sep 17 00:00:00 2001 From: Olivier Gagnon Date: Mon, 16 Aug 2021 18:43:55 -0400 Subject: [PATCH 09/15] Blade is fully converted to React but now it needs refactoring. --- src/Bladeburner.jsx | 240 ------------------ src/Bladeburner/Bladeburner.ts | 212 +++++++++++++++- src/Bladeburner/IBladeburner.ts | 34 ++- src/Bladeburner/ui/Console.tsx | 2 +- src/DevMenu.jsx | 2 +- src/NetscriptFunctions.js | 4 +- .../Player/PlayerObjectBladeburnerMethods.js | 4 +- src/Prestige.js | 2 +- src/engine.jsx | 2 +- 9 files changed, 243 insertions(+), 259 deletions(-) delete mode 100644 src/Bladeburner.jsx diff --git a/src/Bladeburner.jsx b/src/Bladeburner.jsx deleted file mode 100644 index 3a30b91fa..000000000 --- a/src/Bladeburner.jsx +++ /dev/null @@ -1,240 +0,0 @@ -import { Augmentations } from "./Augmentation/Augmentations"; -import { AugmentationNames } from "./Augmentation/data/AugmentationNames"; -import { BitNodeMultipliers } from "./BitNode/BitNodeMultipliers"; -import { Engine } from "./engine"; -import { Faction } from "./Faction/Faction"; -import { Factions, factionExists } from "./Faction/Factions"; -import { joinFaction, displayFactionContent } from "./Faction/FactionHelpers"; -import { Player } from "./Player"; -import { hackWorldDaemon, redPillFlag } from "./RedPill"; -import { calculateHospitalizationCost } from "./Hospital/Hospital"; - -import { Page, routing } from "./ui/navigationTracking"; -import { numeralWrapper } from "./ui/numeralFormat"; - -import { dialogBoxCreate } from "../utils/DialogBox"; -import { - Reviver, - Generic_toJSON, - Generic_fromJSON, -} from "../utils/JSONReviver"; -import { setTimeoutRef } from "./utils/SetTimeoutRef"; -import { - formatNumber, - convertTimeMsToTimeElapsedString, -} from "../utils/StringHelperFunctions"; - -import { Settings } from "./Settings/Settings"; -import { City } from "./Bladeburner/City"; -import { BladeburnerConstants } from "./Bladeburner/data/Constants"; -import { Skill } from "./Bladeburner/Skill"; -import { Skills } from "./Bladeburner/Skills"; -import { Operation } from "./Bladeburner/Operation"; -import { BlackOperation } from "./Bladeburner/BlackOperation"; -import { BlackOperations } from "./Bladeburner/BlackOperations"; -import { Contract } from "./Bladeburner/Contract"; -import { GeneralActions } from "./Bladeburner/GeneralActions"; -import { ActionTypes } from "./Bladeburner/data/ActionTypes"; -import { ActionIdentifier } from "./Bladeburner/ActionIdentifier"; - -import { addOffset } from "../utils/helpers/addOffset"; -import { clearObject } from "../utils/helpers/clearObject"; -import { createProgressBarText } from "../utils/helpers/createProgressBarText"; -import { exceptionAlert } from "../utils/helpers/exceptionAlert"; -import { getRandomInt } from "../utils/helpers/getRandomInt"; -import { getTimestamp } from "../utils/helpers/getTimestamp"; -import { KEY } from "../utils/helpers/keyCodes"; - -import { removeChildrenFromElement } from "../utils/uiHelpers/removeChildrenFromElement"; -import { appendLineBreaks } from "../utils/uiHelpers/appendLineBreaks"; -import { createElement } from "../utils/uiHelpers/createElement"; -import { createPopup } from "../utils/uiHelpers/createPopup"; -import { removeElement } from "../utils/uiHelpers/removeElement"; -import { removeElementById } from "../utils/uiHelpers/removeElementById"; - -import { Stats } from "./Bladeburner/ui/Stats"; -import { AllPages } from "./Bladeburner/ui/AllPages"; -import { Console } from "./Bladeburner/ui/Console"; -import { Root } from "./Bladeburner/ui/Root"; - -import { StatsTable } from "./ui/React/StatsTable"; -import { CopyableText } from "./ui/React/CopyableText"; -import { Money } from "./ui/React/Money"; -import React from "react"; -import ReactDOM from "react-dom"; - -import { - getActionIdFromTypeAndName, - executeStartConsoleCommand, - executeSkillConsoleCommand, - executeLogConsoleCommand, - executeHelpConsoleCommand, - executeAutomateConsoleCommand, - parseCommandArguments, - executeConsoleCommand, - executeConsoleCommands, - triggerMigration, - triggerPotentialMigration, - randomEvent, - gainActionStats, - getDiplomacyEffectiveness, - getRecruitmentSuccessChance, - getRecruitmentTime, - resetSkillMultipliers, - updateSkillMultipliers, - resetAction, - getActionObject, - completeOperation, - completeContract, - completeAction, - processAction, - startAction, - calculateStaminaGainPerSecond, - calculateMaxStamina, - create, - prestige, - storeCycles, - getCurrentCity, - upgradeSkill, - postToConsole, - log, - calculateStaminaPenalty, - getTypeAndNameFromActionId, - getContractNamesNetscriptFn, - getOperationNamesNetscriptFn, - getBlackOpNamesNetscriptFn, - getGeneralActionNamesNetscriptFn, - getSkillNamesNetscriptFn, - startActionNetscriptFn, - getActionTimeNetscriptFn, - getActionEstimatedSuccessChanceNetscriptFn, - getActionCountRemainingNetscriptFn, - getSkillLevelNetscriptFn, - getSkillUpgradeCostNetscriptFn, - upgradeSkillNetscriptFn, - getTeamSizeNetscriptFn, - setTeamSizeNetscriptFn, - joinBladeburnerFactionNetscriptFn, -} from "./Bladeburner/Bladeburner"; - -function Bladeburner(params={}) { - this.numHosp = 0; // Number of hospitalizations - this.moneyLost = 0; // Money lost due to hospitalizations - this.rank = 0; - this.maxRank = 0; // Used to determine skill points - - this.skillPoints = 0; - this.totalSkillPoints = 0; - - this.teamSize = 0; // Number of team members - this.teamLost = 0; // Number of team members lost - - this.storedCycles = 0; - - this.randomEventCounter = getRandomInt(240, 600); // 4-10 minutes - - // These times are in seconds - this.actionTimeToComplete = 0; // 0 or -1 is an infinite running action (like training) - this.actionTimeCurrent = 0; - this.actionTimeOverflow = 0; - - // ActionIdentifier Object - var idleActionType = ActionTypes["Idle"]; - this.action = new ActionIdentifier({type:idleActionType}); - - this.cities = {}; - for (var i = 0; i < BladeburnerConstants.CityNames.length; ++i) { - this.cities[BladeburnerConstants.CityNames[i]] = new City({name: BladeburnerConstants.CityNames[i]}); - } - this.city = BladeburnerConstants.CityNames[2]; // Sector-12 - - // Map of SkillNames -> level - this.skills = {}; - this.skillMultipliers = {}; - updateSkillMultipliers(this); // Calls resetSkillMultipliers() - - // Max Stamina is based on stats and Bladeburner-specific bonuses - this.staminaBonus = 0; // Gained from training - this.maxStamina = 0; - calculateMaxStamina(this, Player); - this.stamina = this.maxStamina; - - /** - * Contracts and Operations objects. These objects have unique - * properties because they are randomized in each instance and have stats like - * successes/failures, so they need to be saved/loaded by the game. - */ - this.contracts = {}; - this.operations = {}; - - // Object that contains name of all Black Operations that have been completed - this.blackops = {}; - - // Flags for whether these actions should be logged to console - this.logging = { - general:true, - contracts:true, - ops:true, - blackops:true, - events:true, - } - - // Simple automation values - this.automateEnabled = false; - this.automateActionHigh = 0; - this.automateThreshHigh = 0; //Stamina Threshold - this.automateActionLow = 0; - this.automateThreshLow = 0; //Stamina Threshold - - // Console command history - this.consoleHistory = []; - this.consoleLogs = [ - "Bladeburner Console", - "Type 'help' to see console commands", - ]; - - // Initialization - if (params.new) create(this); -} - -Bladeburner.prototype.prestige = function() { prestige(this); } -Bladeburner.prototype.storeCycles = function(numCycles=1) { storeCycles(this, numCycles); } -Bladeburner.prototype.calculateStaminaPenalty = function() { return calculateStaminaPenalty(this); } -Bladeburner.prototype.getCurrentCity = function() { return getCurrentCity(this); } -Bladeburner.prototype.upgradeSkill = function(skill) { upgradeSkill(this, skill); } -// Bladeburner Console Window -Bladeburner.prototype.postToConsole = function(input, saveToLogs=true) { postToConsole(this, input, saveToLogs); } -Bladeburner.prototype.log = function(input) { log(this, input); } -// Handles a potential series of commands (comm1; comm2; comm3;) -Bladeburner.prototype.executeConsoleCommands = function(commands) { executeConsoleCommands(this, Player, commands); } - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////// Netscript Fns ///////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - -Bladeburner.prototype.getTypeAndNameFromActionId = function(actionId) { getTypeAndNameFromActionId(this, actionId); } -Bladeburner.prototype.getContractNamesNetscriptFn = function() { getContractNamesNetscriptFn(this); } -Bladeburner.prototype.getOperationNamesNetscriptFn = function() { getOperationNamesNetscriptFn(this); } -Bladeburner.prototype.getBlackOpNamesNetscriptFn = function() { getBlackOpNamesNetscriptFn(this); } -Bladeburner.prototype.getGeneralActionNamesNetscriptFn = function() { getGeneralActionNamesNetscriptFn(this); } -Bladeburner.prototype.getSkillNamesNetscriptFn = function() { getSkillNamesNetscriptFn(this); } -Bladeburner.prototype.startActionNetscriptFn = function(type, name, workerScript) { startActionNetscriptFn(this, Player, type, name, workerScript); } -Bladeburner.prototype.getActionTimeNetscriptFn = function(type, name, workerScript) { getActionTimeNetscriptFn(this, Player, type, name, workerScript); } -Bladeburner.prototype.getActionEstimatedSuccessChanceNetscriptFn = function(type, name, workerScript) { getActionEstimatedSuccessChanceNetscriptFn(this, Player, type, name, workerScript); } -Bladeburner.prototype.getActionCountRemainingNetscriptFn = function(type, name, workerScript) { getActionCountRemainingNetscriptFn(this, Player, type, name, workerScript); } -Bladeburner.prototype.getSkillLevelNetscriptFn = function(skillName, workerScript) { getSkillLevelNetscriptFn(this, skillName, workerScript); } -Bladeburner.prototype.getSkillUpgradeCostNetscriptFn = function(skillName, workerScript) { getSkillUpgradeCostNetscriptFn(this, skillName, workerScript); } -Bladeburner.prototype.upgradeSkillNetscriptFn = function(skillName, workerScript) { upgradeSkillNetscriptFn(this, skillName, workerScript); } -Bladeburner.prototype.getTeamSizeNetscriptFn = function(type, name, workerScript) { getTeamSizeNetscriptFn(this, type, name, workerScript); } -Bladeburner.prototype.setTeamSizeNetscriptFn = function(type, name, size, workerScript) { setTeamSizeNetscriptFn(this, type, name, size, workerScript); } -Bladeburner.prototype.joinBladeburnerFactionNetscriptFn = function(workerScript) { joinBladeburnerFactionNetscriptFn(this, workerScript); } - -Bladeburner.prototype.toJSON = function() { - return Generic_toJSON("Bladeburner", this); -} -Bladeburner.fromJSON = function(value) { - return Generic_fromJSON(Bladeburner, value.data); -} -Reviver.constructors.Bladeburner = Bladeburner; - -export { Bladeburner }; diff --git a/src/Bladeburner/Bladeburner.ts b/src/Bladeburner/Bladeburner.ts index f5debedd0..3fb3f6536 100644 --- a/src/Bladeburner/Bladeburner.ts +++ b/src/Bladeburner/Bladeburner.ts @@ -2,6 +2,11 @@ Here we have a bunch of functions converted to typescript, eventually they will go back into a Bladeburner class. */ +import { + Reviver, + Generic_toJSON, + Generic_fromJSON, +} from "../../utils/JSONReviver"; import { IBladeburner } from "./IBladeburner"; import { IActionIdentifier } from "./IActionIdentifier"; import { ActionIdentifier } from "./ActionIdentifier"; @@ -36,6 +41,197 @@ import { getTimestamp } from "../../utils/helpers/getTimestamp"; import { joinFaction } from "../Faction/FactionHelpers"; import { WorkerScript } from "../Netscript/WorkerScript"; +export class Bladeburner implements IBladeburner { + numHosp: number = 0; + moneyLost: number = 0; + rank: number = 0; + maxRank: number = 0; + + skillPoints: number = 0; + totalSkillPoints: number = 0; + + teamSize: number = 0; + teamLost: number = 0; + hpLost: number = 0; + + storedCycles: number = 0; + + randomEventCounter: number = getRandomInt(240, 600); + + actionTimeToComplete: number = 0; + actionTimeCurrent: number = 0; + actionTimeOverflow: number = 0; + + action: IActionIdentifier = new ActionIdentifier({type: ActionTypes["Idle"]}); + + cities: any = {}; + city: string = BladeburnerConstants.CityNames[2]; + skills: any = {}; + skillMultipliers: any = {}; + staminaBonus: number = 0; + maxStamina: number = 0; + stamina: number = 0; + contracts: any = {}; + operations: any = {}; + blackops: any = {}; + logging: any = { + general:true, + contracts:true, + ops:true, + blackops:true, + events:true, + }; + automateEnabled: boolean = false; + automateActionHigh: IActionIdentifier = new ActionIdentifier({type: ActionTypes["Idle"]}); + automateThreshHigh: number = 0; + automateActionLow: IActionIdentifier = new ActionIdentifier({type: ActionTypes["Idle"]}); + automateThreshLow: number = 0; + consoleHistory: string[] = []; + consoleLogs: string[] = [ + "Bladeburner Console", + "Type 'help' to see console commands", + ]; + + constructor(player?: IPlayer) { + for (var i = 0; i < BladeburnerConstants.CityNames.length; ++i) { + this.cities[BladeburnerConstants.CityNames[i]] = new City(BladeburnerConstants.CityNames[i]); + } + + updateSkillMultipliers(this); // Calls resetSkillMultipliers() + + // Max Stamina is based on stats and Bladeburner-specific bonuses + if(player) + calculateMaxStamina(this, player); + this.stamina = this.maxStamina; + create(this); + } + + getCurrentCity(): City { + return getCurrentCity(this); + } + + calculateStaminaPenalty(): number { + return calculateStaminaPenalty(this); + } + + startAction(player: IPlayer, action: IActionIdentifier): void { + startAction(this, player, action); + } + + upgradeSkill(skill: Skill): void { + upgradeSkill(this, skill); + } + + executeConsoleCommands(player: IPlayer, command: string): void { + executeConsoleCommands(this, player, command); + } + + postToConsole(input: string, saveToLogs?: boolean): void { + postToConsole(this, input, saveToLogs); + } + + log(input: string): void { + log(this, input); + } + + resetAction(): void { + resetAction(this); + } + + clearConsole(): void { + clearConsole(this); + } + + prestige(): void { + prestige(this); + } + + storeCycles(numCycles?: number): void { + storeCycles(this, numCycles); + } + + getTypeAndNameFromActionId(actionId: IActionIdentifier): {type: string, name: string} { + return getTypeAndNameFromActionId(this, actionId); + } + + getContractNamesNetscriptFn(): string[] { + return getContractNamesNetscriptFn(this); + } + + getOperationNamesNetscriptFn(): string[] { + return getOperationNamesNetscriptFn(this); + } + + getBlackOpNamesNetscriptFn(): string[] { + return getBlackOpNamesNetscriptFn(this); + } + + getGeneralActionNamesNetscriptFn(): string[] { + return getGeneralActionNamesNetscriptFn(this); + } + + getSkillNamesNetscriptFn(): string[] { + return getSkillNamesNetscriptFn(this); + } + + startActionNetscriptFn(player: IPlayer, type: string, name: string, workerScript: WorkerScript): boolean { + return startActionNetscriptFn(this, player, type, name, workerScript); + } + + getActionTimeNetscriptFn(player: IPlayer, type: string, name: string, workerScript: WorkerScript): number { + return getActionTimeNetscriptFn(this, player, type, name, workerScript); + } + + getActionEstimatedSuccessChanceNetscriptFn(player: IPlayer, type: string, name: string, workerScript: WorkerScript): number { + return getActionEstimatedSuccessChanceNetscriptFn(this, player, type, name, workerScript); + } + + getActionCountRemainingNetscriptFn(type: string, name: string, workerScript: WorkerScript): number { + return getActionCountRemainingNetscriptFn(this, type, name, workerScript); + } + + getSkillLevelNetscriptFn(skillName: string, workerScript: WorkerScript): number { + return getSkillLevelNetscriptFn(this, skillName, workerScript); + } + + getSkillUpgradeCostNetscriptFn(skillName: string, workerScript: WorkerScript): number { + return getSkillUpgradeCostNetscriptFn(this, skillName, workerScript); + } + + upgradeSkillNetscriptFn(skillName: string, workerScript: WorkerScript): boolean { + return upgradeSkillNetscriptFn(this, skillName, workerScript); + } + + getTeamSizeNetscriptFn(type: string, name: string, workerScript: WorkerScript): number { + return getTeamSizeNetscriptFn(this, type, name, workerScript); + } + + setTeamSizeNetscriptFn(type: string, name: string, size: number, workerScript: WorkerScript): number { + return setTeamSizeNetscriptFn(this, type, name, size, workerScript); + } + + joinBladeburnerFactionNetscriptFn(workerScript: WorkerScript): boolean { + return joinBladeburnerFactionNetscriptFn(this, workerScript); + } + + /** + * Serialize the current object to a JSON save state. + */ + toJSON(): any { + return Generic_toJSON("Bladeburner", this); + } + + /** + * Initiatizes a Bladeburner object from a JSON save state. + */ + // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types + static fromJSON(value: any): Bladeburner { + return Generic_fromJSON(Bladeburner, value.data); + } +} + +Reviver.constructors.Bladeburner = Bladeburner; + export function getActionIdFromTypeAndName(bladeburner: IBladeburner, type: string = "", name: string = ""): IActionIdentifier | null { if (type === "" || name === "") {return null;} const action = new ActionIdentifier(); @@ -134,7 +330,7 @@ export function executeStartConsoleCommand(bladeburner: IBladeburner, player: IP if (GeneralActions[name] != null) { bladeburner.action.type = ActionTypes[name]; bladeburner.action.name = name; - startAction(bladeburner,player, bladeburner.action); + startAction(bladeburner, player, bladeburner.action); } else { bladeburner.postToConsole("Invalid action name specified: " + args[2]); } @@ -144,7 +340,7 @@ export function executeStartConsoleCommand(bladeburner: IBladeburner, player: IP if (bladeburner.contracts[name] != null) { bladeburner.action.type = ActionTypes.Contract; bladeburner.action.name = name; - startAction(bladeburner,player, bladeburner.action); + startAction(bladeburner, player, bladeburner.action); } else { bladeburner.postToConsole("Invalid contract name specified: " + args[2]); } @@ -1602,7 +1798,7 @@ export function log(bladeburner: IBladeburner, input: string) { //////////////////////////////////////////////////////////////////////////////// //////////////////////////////// Netscript Fns ///////////////////////////////// //////////////////////////////////////////////////////////////////////////////// -export function getTypeAndNameFromActionId(bladeburner: IBladeburner, actionId: IActionIdentifier) { +export function getTypeAndNameFromActionId(bladeburner: IBladeburner, actionId: IActionIdentifier): {type: string, name: string} { const res = {type: '', name: ''}; const types = Object.keys(ActionTypes); for (let i = 0; i < types.length; ++i) { @@ -1616,19 +1812,19 @@ export function getTypeAndNameFromActionId(bladeburner: IBladeburner, actionId: res.name = actionId.name != null ? actionId.name : "Idle"; return res; } -export function getContractNamesNetscriptFn(bladeburner: IBladeburner) { +export function getContractNamesNetscriptFn(bladeburner: IBladeburner): string[] { return Object.keys(bladeburner.contracts); } -export function getOperationNamesNetscriptFn(bladeburner: IBladeburner) { +export function getOperationNamesNetscriptFn(bladeburner: IBladeburner): string[] { return Object.keys(bladeburner.operations); } -export function getBlackOpNamesNetscriptFn(bladeburner: IBladeburner) { +export function getBlackOpNamesNetscriptFn(bladeburner: IBladeburner): string[] { return Object.keys(BlackOperations); } -export function getGeneralActionNamesNetscriptFn(bladeburner: IBladeburner) { +export function getGeneralActionNamesNetscriptFn(bladeburner: IBladeburner): string[] { return Object.keys(GeneralActions); } -export function getSkillNamesNetscriptFn(bladeburner: IBladeburner) { +export function getSkillNamesNetscriptFn(bladeburner: IBladeburner): string[] { return Object.keys(Skills); } export function startActionNetscriptFn(bladeburner: IBladeburner, player: IPlayer, type: string, name: string, workerScript: WorkerScript) { diff --git a/src/Bladeburner/IBladeburner.ts b/src/Bladeburner/IBladeburner.ts index b8d65909c..a9cb20e01 100644 --- a/src/Bladeburner/IBladeburner.ts +++ b/src/Bladeburner/IBladeburner.ts @@ -1,23 +1,32 @@ import { IActionIdentifier } from "./IActionIdentifier"; import { City } from "./City"; import { Skill } from "./Skill"; +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; @@ -39,11 +48,30 @@ export interface IBladeburner { getCurrentCity(): City; calculateStaminaPenalty(): number; - startAction(action: IActionIdentifier): void; + startAction(player: IPlayer, action: IActionIdentifier): void; upgradeSkill(skill: Skill): void; - executeConsoleCommands(command: string): void; + executeConsoleCommands(player: IPlayer, command: string): void; postToConsole(input: string, saveToLogs?: boolean): void; log(input: string): void; resetAction(): void; clearConsole(): void; -} \ No newline at end of file + + 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; +} diff --git a/src/Bladeburner/ui/Console.tsx b/src/Bladeburner/ui/Console.tsx index 8cbee7181..b09392218 100644 --- a/src/Bladeburner/ui/Console.tsx +++ b/src/Bladeburner/ui/Console.tsx @@ -50,7 +50,7 @@ export function Console(props: IProps): React.ReactElement { event.currentTarget.value = ""; if (command.length > 0) { props.bladeburner.postToConsole("> " + command); - props.bladeburner.executeConsoleCommands(command); + props.bladeburner.executeConsoleCommands(props.player, command); setConsoleHistoryIndex(props.bladeburner.consoleHistory.length); rerender(); } diff --git a/src/DevMenu.jsx b/src/DevMenu.jsx index 8469995e6..ad14b034d 100644 --- a/src/DevMenu.jsx +++ b/src/DevMenu.jsx @@ -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"; diff --git a/src/NetscriptFunctions.js b/src/NetscriptFunctions.js index 0b03a1c22..2e3e11da3 100644 --- a/src/NetscriptFunctions.js +++ b/src/NetscriptFunctions.js @@ -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"); diff --git a/src/PersonObjects/Player/PlayerObjectBladeburnerMethods.js b/src/PersonObjects/Player/PlayerObjectBladeburnerMethods.js index 526cc34fe..2a4754701 100644 --- a/src/PersonObjects/Player/PlayerObjectBladeburnerMethods.js +++ b/src/PersonObjects/Player/PlayerObjectBladeburnerMethods.js @@ -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); } diff --git a/src/Prestige.js b/src/Prestige.js index 1362a129a..899f936ec 100755 --- a/src/Prestige.js +++ b/src/Prestige.js @@ -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"; diff --git a/src/engine.jsx b/src/engine.jsx index 9ae80b83a..1c5379aea 100644 --- a/src/engine.jsx +++ b/src/engine.jsx @@ -17,7 +17,7 @@ import { AugmentationNames } from "./Augmentation/data/AugmentationNames"; import { initBitNodeMultipliers, } from "./BitNode/BitNode"; -import { Bladeburner } from "./Bladeburner"; +import { Bladeburner } from "./Bladeburner/Bladeburner"; import { process as ProcessBladeburner } from "./Bladeburner/Bladeburner"; import { CharacterOverviewComponent } from "./ui/React/CharacterOverview"; import { cinematicTextFlag } from "./CinematicText"; From 99afb156fa816325d428ef6666b99d2752f8a74e Mon Sep 17 00:00:00 2001 From: Olivier Gagnon Date: Mon, 16 Aug 2021 19:44:21 -0400 Subject: [PATCH 10/15] Refactoring mostly done, still a few bugs and test to do. --- src/Bladeburner/Bladeburner.ts | 3741 +++++++++++----------- src/Bladeburner/IBladeburner.ts | 30 +- src/Bladeburner/ui/BlackOpElem.tsx | 3 +- src/Bladeburner/ui/ContractElem.tsx | 7 +- src/Bladeburner/ui/GeneralActionElem.tsx | 4 +- src/Bladeburner/ui/OperationElem.tsx | 7 +- src/engine.jsx | 2 +- 7 files changed, 1846 insertions(+), 1948 deletions(-) diff --git a/src/Bladeburner/Bladeburner.ts b/src/Bladeburner/Bladeburner.ts index 3fb3f6536..00bb64764 100644 --- a/src/Bladeburner/Bladeburner.ts +++ b/src/Bladeburner/Bladeburner.ts @@ -1,7 +1,3 @@ -/* - Here we have a bunch of functions converted to typescript, eventually they - will go back into a Bladeburner class. -*/ import { Reviver, Generic_toJSON, @@ -97,122 +93,1879 @@ export class Bladeburner implements IBladeburner { this.cities[BladeburnerConstants.CityNames[i]] = new City(BladeburnerConstants.CityNames[i]); } - updateSkillMultipliers(this); // Calls resetSkillMultipliers() + this.updateSkillMultipliers(); // Calls resetSkillMultipliers() // Max Stamina is based on stats and Bladeburner-specific bonuses if(player) - calculateMaxStamina(this, player); + this.calculateMaxStamina(player); this.stamina = this.maxStamina; - create(this); + this.create(); } getCurrentCity(): City { - return getCurrentCity(this); + const city = this.cities[this.city]; + if (!(city instanceof City)) { + throw new Error("Bladeburner.getCurrentCity() did not properly return a City object"); + } + return city; } calculateStaminaPenalty(): number { - return calculateStaminaPenalty(this); + return Math.min(1, this.stamina / (0.5 * this.maxStamina)); } - startAction(player: IPlayer, action: IActionIdentifier): void { - startAction(this, player, action); + startAction(player: IPlayer, actionId: IActionIdentifier): void { + if (actionId == null) return; + this.action = actionId; + this.actionTimeCurrent = 0; + switch (actionId.type) { + case ActionTypes["Idle"]: + this.actionTimeToComplete = 0; + break; + case ActionTypes["Contract"]: + try { + const action = this.getActionObject(actionId); + if (action == null) { + throw new Error("Failed to get Contract Object for: " + actionId.name); + } + if (action.count < 1) {return this.resetAction();} + this.actionTimeToComplete = action.getActionTime(this); + } catch(e) { + exceptionAlert(e); + } + break; + case ActionTypes["Operation"]: { + try { + const action = this.getActionObject(actionId); + if (action == null) { + throw new Error ("Failed to get Operation Object for: " + actionId.name); + } + if (action.count < 1) {return this.resetAction();} + if (actionId.name === "Raid" && this.getCurrentCity().commsEst === 0) {return this.resetAction();} + this.actionTimeToComplete = action.getActionTime(this); + } catch(e) { + exceptionAlert(e); + } + break; + } + case ActionTypes["BlackOp"]: + case ActionTypes["BlackOperation"]: { + try { + // Safety measure - don't repeat BlackOps that are already done + if (this.blackops[actionId.name] != null) { + this.resetAction(); + this.log("Error: Tried to start a Black Operation that had already been completed"); + break; + } + + const action = this.getActionObject(actionId); + if (action == null) { + throw new Error("Failed to get BlackOperation object for: " + actionId.name); + } + this.actionTimeToComplete = action.getActionTime(this); + } catch(e) { + exceptionAlert(e); + } + break; + } + case ActionTypes["Recruitment"]: + this.actionTimeToComplete = this.getRecruitmentTime(player); + break; + case ActionTypes["Training"]: + case ActionTypes["FieldAnalysis"]: + case ActionTypes["Field Analysis"]: + this.actionTimeToComplete = 30; + break; + case ActionTypes["Diplomacy"]: + case ActionTypes["Hyperbolic Regeneration Chamber"]: + this.actionTimeToComplete = 60; + break; + default: + throw new Error("Invalid Action Type in startAction(Bladeburner,player, ): " + actionId.type); + break; + } } upgradeSkill(skill: Skill): void { - upgradeSkill(this, skill); + // This does NOT handle deduction of skill points + const skillName = skill.name; + if (this.skills[skillName]) { + ++this.skills[skillName]; + } else { + this.skills[skillName] = 1; + } + if (isNaN(this.skills[skillName]) || this.skills[skillName] < 0) { + throw new Error("Level of Skill " + skillName + " is invalid: " + this.skills[skillName]); + } + this.updateSkillMultipliers(); } - executeConsoleCommands(player: IPlayer, command: string): void { - executeConsoleCommands(this, player, command); + executeConsoleCommands(player: IPlayer, commands: string): void { + try { + // Console History + if (this.consoleHistory[this.consoleHistory.length-1] != commands) { + this.consoleHistory.push(commands); + if (this.consoleHistory.length > 50) { + this.consoleHistory.splice(0, 1); + } + } + + const arrayOfCommands = commands.split(";"); + for (let i = 0; i < arrayOfCommands.length; ++i) { + this.executeConsoleCommand(player, arrayOfCommands[i]); + } + } catch(e) { + exceptionAlert(e); + } } postToConsole(input: string, saveToLogs?: boolean): void { - postToConsole(this, input, saveToLogs); + const MaxConsoleEntries = 100; + if (saveToLogs) { + this.consoleLogs.push(input); + if (this.consoleLogs.length > MaxConsoleEntries) { + this.consoleLogs.shift(); + } + } } log(input: string): void { - log(this, input); + // Adds a timestamp and then just calls postToConsole + this.postToConsole(`[${getTimestamp()}] ${input}`); } resetAction(): void { - resetAction(this); + this.action = new ActionIdentifier({type:ActionTypes.Idle}); } clearConsole(): void { - clearConsole(this); + this.consoleLogs.length = 0; } prestige(): void { - prestige(this); + this.resetAction(); + const bladeburnerFac = Factions["Bladeburners"]; + if (this.rank >= BladeburnerConstants.RankNeededForFaction) { + joinFaction(bladeburnerFac); + } } - storeCycles(numCycles?: number): void { - storeCycles(this, numCycles); + storeCycles(numCycles: number = 0): void { + this.storedCycles += numCycles; + } + + + // working on + getActionIdFromTypeAndName(type: string = "", name: string = ""): IActionIdentifier | null { + if (type === "" || name === "") {return null;} + const action = new ActionIdentifier(); + const convertedType = type.toLowerCase().trim(); + const convertedName = name.toLowerCase().trim(); + switch (convertedType) { + case "contract": + case "contracts": + case "contr": + action.type = ActionTypes["Contract"]; + if (this.contracts.hasOwnProperty(name)) { + action.name = name; + return action; + } else { + return null; + } + break; + case "operation": + case "operations": + case "op": + case "ops": + action.type = ActionTypes["Operation"]; + if (this.operations.hasOwnProperty(name)) { + action.name = name; + return action; + } else { + return null; + } + break; + case "blackoperation": + case "black operation": + case "black operations": + case "black op": + case "black ops": + case "blackop": + case "blackops": + action.type = ActionTypes["BlackOp"]; + if (BlackOperations.hasOwnProperty(name)) { + action.name = name; + return action; + } else { + return null; + } + break; + case "general": + case "general action": + case "gen": + break; + default: + return null; + } + + if (convertedType.startsWith("gen")) { + switch (convertedName) { + case "training": + action.type = ActionTypes["Training"]; + action.name = "Training"; + break; + case "recruitment": + case "recruit": + action.type = ActionTypes["Recruitment"]; + action.name = "Recruitment"; + break; + case "field analysis": + case "fieldanalysis": + action.type = ActionTypes["Field Analysis"]; + action.name = "Field Analysis"; + break; + case "diplomacy": + action.type = ActionTypes["Diplomacy"]; + action.name = "Diplomacy"; + break; + case "hyperbolic regeneration chamber": + action.type = ActionTypes["Hyperbolic Regeneration Chamber"]; + action.name = "Hyperbolic Regeneration Chamber"; + break; + default: + return null; + } + return action; + } + + return null; + } + + executeStartConsoleCommand(player: IPlayer, args: string[]): void { + if (args.length !== 3) { + this.postToConsole("Invalid usage of 'start' console command: start [type] [name]"); + this.postToConsole("Use 'help start' for more info"); + return; + } + const name = args[2]; + switch (args[1].toLowerCase()) { + case "general": + case "gen": + if (GeneralActions[name] != null) { + this.action.type = ActionTypes[name]; + this.action.name = name; + this.startAction(player, this.action); + } else { + this.postToConsole("Invalid action name specified: " + args[2]); + } + break; + case "contract": + case "contracts": + if (this.contracts[name] != null) { + this.action.type = ActionTypes.Contract; + this.action.name = name; + this.startAction(player, this.action); + } else { + this.postToConsole("Invalid contract name specified: " + args[2]); + } + break; + case "ops": + case "op": + case "operations": + case "operation": + if (this.operations[name] != null) { + this.action.type = ActionTypes.Operation; + this.action.name = name; + this.startAction(player, this.action); + } else { + this.postToConsole("Invalid Operation name specified: " + args[2]); + } + break; + case "blackops": + case "blackop": + case "black operations": + case "black operation": + if (BlackOperations[name] != null) { + this.action.type = ActionTypes.BlackOperation; + this.action.name = name; + this.startAction(player, this.action); + } else { + this.postToConsole("Invalid BlackOp name specified: " + args[2]); + } + break; + default: + this.postToConsole("Invalid action/event type specified: " + args[1]); + this.postToConsole("Examples of valid action/event identifiers are: [general, contract, op, blackop]"); + break; + } + } + + executeSkillConsoleCommand(args: string[]): void { + switch (args.length) { + case 1: + // Display Skill Help Command + this.postToConsole("Invalid usage of 'skill' console command: skill [action] [name]"); + this.postToConsole("Use 'help skill' for more info"); + break; + case 2: + if (args[1].toLowerCase() === "list") { + // List all skills and their level + this.postToConsole("Skills: "); + const skillNames = Object.keys(Skills); + for(let i = 0; i < skillNames.length; ++i) { + let skill = Skills[skillNames[i]]; + let level = 0; + if (this.skills[skill.name] != null) {level = this.skills[skill.name];} + this.postToConsole(skill.name + ": Level " + formatNumber(level, 0)); + } + this.postToConsole(" "); + this.postToConsole("Effects: "); + const multKeys = Object.keys(this.skillMultipliers); + for (let i = 0; i < multKeys.length; ++i) { + let mult = this.skillMultipliers[multKeys[i]]; + if (mult && mult !== 1) { + mult = formatNumber(mult, 3); + switch(multKeys[i]) { + case "successChanceAll": + this.postToConsole("Total Success Chance: x" + mult); + break; + case "successChanceStealth": + this.postToConsole("Stealth Success Chance: x" + mult); + break; + case "successChanceKill": + this.postToConsole("Retirement Success Chance: x" + mult); + break; + case "successChanceContract": + this.postToConsole("Contract Success Chance: x" + mult); + break; + case "successChanceOperation": + this.postToConsole("Operation Success Chance: x" + mult); + break; + case "successChanceEstimate": + this.postToConsole("Synthoid Data Estimate: x" + mult); + break; + case "actionTime": + this.postToConsole("Action Time: x" + mult); + break; + case "effHack": + this.postToConsole("Hacking Skill: x" + mult); + break; + case "effStr": + this.postToConsole("Strength: x" + mult); + break; + case "effDef": + this.postToConsole("Defense: x" + mult); + break; + case "effDex": + this.postToConsole("Dexterity: x" + mult); + break; + case "effAgi": + this.postToConsole("Agility: x" + mult); + break; + case "effCha": + this.postToConsole("Charisma: x" + mult); + break; + case "effInt": + this.postToConsole("Intelligence: x" + mult); + break; + case "stamina": + this.postToConsole("Stamina: x" + mult); + break; + default: + console.warn(`Unrecognized SkillMult Key: ${multKeys[i]}`); + break; + } + } + } + } else { + this.postToConsole("Invalid usage of 'skill' console command: skill [action] [name]"); + this.postToConsole("Use 'help skill' for more info"); + } + break; + case 3: + const skillName = args[2]; + const skill = Skills[skillName]; + if (skill == null || !(skill instanceof Skill)) { + this.postToConsole("Invalid skill name (Note that it is case-sensitive): " + skillName); + } + if (args[1].toLowerCase() === "list") { + let level = 0; + if (this.skills[skill.name] !== undefined) { + level = this.skills[skill.name]; + } + this.postToConsole(skill.name + ": Level " + formatNumber(level)); + } else if (args[1].toLowerCase() === "level") { + let currentLevel = 0; + if (this.skills[skillName] && !isNaN(this.skills[skillName])) { + currentLevel = this.skills[skillName]; + } + const pointCost = skill.calculateCost(currentLevel); + if (this.skillPoints >= pointCost) { + this.skillPoints -= pointCost; + this.upgradeSkill(skill); + this.log(skill.name + " upgraded to Level " + this.skills[skillName]); + } else { + this.postToConsole("You do not have enough Skill Points to upgrade this. You need " + formatNumber(pointCost, 0)); + } + + } else { + this.postToConsole("Invalid usage of 'skill' console command: skill [action] [name]"); + this.postToConsole("Use 'help skill' for more info"); + } + break; + default: + this.postToConsole("Invalid usage of 'skill' console command: skill [action] [name]"); + this.postToConsole("Use 'help skill' for more info"); + break; + } + } + + executeLogConsoleCommand(args: string[]): void { + if (args.length < 3) { + this.postToConsole("Invalid usage of log command: log [enable/disable] [action/event]"); + this.postToConsole("Use 'help log' for more details and examples"); + return; + } + + let flag = true; + if (args[1].toLowerCase().includes("d")) {flag = false;} // d for disable + + switch (args[2].toLowerCase()) { + case "general": + case "gen": + this.logging.general = flag; + this.log("Logging " + (flag ? "enabled" : "disabled") + " for general actions"); + break; + case "contract": + case "contracts": + this.logging.contracts = flag; + this.log("Logging " + (flag ? "enabled" : "disabled") + " for Contracts"); + break; + case "ops": + case "op": + case "operations": + case "operation": + this.logging.ops = flag; + this.log("Logging " + (flag ? "enabled" : "disabled") + " for Operations"); + break; + case "blackops": + case "blackop": + case "black operations": + case "black operation": + this.logging.blackops = flag; + this.log("Logging " + (flag ? "enabled" : "disabled") + " for BlackOps"); + break; + case "event": + case "events": + this.logging.events = flag; + this.log("Logging " + (flag ? "enabled" : "disabled") + " for events"); + break; + case "all": + this.logging.general = flag; + this.logging.contracts = flag; + this.logging.ops = flag; + this.logging.blackops = flag; + this.logging.events = flag; + this.log("Logging " + (flag ? "enabled" : "disabled") + " for everything"); + break; + default: + this.postToConsole("Invalid action/event type specified: " + args[2]); + this.postToConsole("Examples of valid action/event identifiers are: [general, contracts, ops, blackops, events]"); + break; + } + } + + executeHelpConsoleCommand(args: string[]): void { + if (args.length === 1) { + for(const line of ConsoleHelpText.helpList){ + this.postToConsole(line); + } + } else { + for (let i = 1; i < args.length; ++i) { + if(!(args[i] in ConsoleHelpText)) continue; + const helpText = ConsoleHelpText[args[i]]; + for(const line of helpText){ + this.postToConsole(line); + } + } + } + } + + executeAutomateConsoleCommand(args: string[]): void { + if (args.length !== 2 && args.length !== 4) { + this.postToConsole("Invalid use of 'automate' command: automate [var] [val] [hi/low]. Use 'help automate' for more info"); + return; + } + + // Enable/Disable + if (args.length === 2) { + const flag = args[1]; + if (flag.toLowerCase() === "status") { + this.postToConsole("Automation: " + (this.automateEnabled ? "enabled" : "disabled")); + if (this.automateEnabled) { + this.postToConsole("When your stamina drops to " + formatNumber(this.automateThreshLow, 0) + + ", you will automatically switch to " + this.automateActionLow.name + + ". When your stamina recovers to " + + formatNumber(this.automateThreshHigh, 0) + ", you will automatically " + + "switch to " + this.automateActionHigh.name + "."); + } + + } else if (flag.toLowerCase().includes("en")) { + if (!(this.automateActionLow instanceof ActionIdentifier) || + !(this.automateActionHigh instanceof ActionIdentifier)) { + return this.log("Failed to enable automation. Actions were not set"); + } + this.automateEnabled = true; + this.log("Bladeburner automation enabled"); + } else if (flag.toLowerCase().includes("d")) { + this.automateEnabled = false; + this.log("Bladeburner automation disabled"); + } else { + this.log("Invalid argument for 'automate' console command: " + args[1]); + } + return; + } + + // Set variables + if (args.length === 4) { + const variable = args[1]; + const val = args[2]; + + let highLow = false; // True for high, false for low + if (args[3].toLowerCase().includes("hi")) {highLow = true;} + + switch (variable) { + case "general": + case "gen": + if (GeneralActions[val] != null) { + const action = new ActionIdentifier({ + type:ActionTypes[val], name:val, + }); + if (highLow) { + this.automateActionHigh = action; + } else { + this.automateActionLow = action; + } + this.log("Automate (" + (highLow ? "HIGH" : "LOW") + ") action set to " + val); + } else { + this.postToConsole("Invalid action name specified: " + val); + } + break; + case "contract": + case "contracts": + if (this.contracts[val] != null) { + const action = new ActionIdentifier({ + type:ActionTypes.Contract, name:val, + }); + if (highLow) { + this.automateActionHigh = action; + } else { + this.automateActionLow = action; + } + this.log("Automate (" + (highLow ? "HIGH" : "LOW") + ") action set to " + val); + } else { + this.postToConsole("Invalid contract name specified: " + val); + } + break; + case "ops": + case "op": + case "operations": + case "operation": + if (this.operations[val] != null) { + const action = new ActionIdentifier({ + type:ActionTypes.Operation, name:val, + }); + if (highLow) { + this.automateActionHigh = action; + } else { + this.automateActionLow = action; + } + this.log("Automate (" + (highLow ? "HIGH" : "LOW") + ") action set to " + val); + } else { + this.postToConsole("Invalid Operation name specified: " + val); + } + break; + case "stamina": + if (isNaN(parseFloat(val))) { + this.postToConsole("Invalid value specified for stamina threshold (must be numeric): " + val); + } else { + if (highLow) { + this.automateThreshHigh = Number(val); + } else { + this.automateThreshLow = Number(val); + } + this.log("Automate (" + (highLow ? "HIGH" : "LOW") + ") stamina threshold set to " + val); + } + break; + default: + break; + } + + return; + } + } + + parseCommandArguments(command: string): string[] { + /** + * Returns an array with command and its arguments in each index. + * e.g. skill "blade's intuition" foo returns [skill, blade's intuition, foo] + * The input to the fn will be trimmed and will have all whitespace replaced w/ a single space + */ + const args = []; + let start = 0; + let i = 0; + while (i < command.length) { + const c = command.charAt(i); + if (c === '"') { // Double quotes + const endQuote = command.indexOf('"', i+1); + if (endQuote !== -1 && (endQuote === command.length-1 || command.charAt(endQuote+1) === " ")) { + args.push(command.substr(i+1, (endQuote - i - 1))); + if (endQuote === command.length-1) { + start = i = endQuote+1; + } else { + start = i = endQuote+2; // Skip the space + } + continue; + } + } else if (c === "'") { // Single quotes, same thing as above + const endQuote = command.indexOf("'", i+1); + if (endQuote !== -1 && (endQuote === command.length-1 || command.charAt(endQuote+1) === " ")) { + args.push(command.substr(i+1, (endQuote - i - 1))); + if (endQuote === command.length-1) { + start = i = endQuote+1; + } else { + start = i = endQuote+2; // Skip the space + } + continue; + } + } else if (c === " ") { + args.push(command.substr(start, i-start)); + start = i+1; + } + ++i; + } + if (start !== i) {args.push(command.substr(start, i-start));} + return args; + } + + executeConsoleCommand(player: IPlayer, command: string) { + command = command.trim(); + command = command.replace(/\s\s+/g, ' '); // Replace all whitespace w/ a single space + + const args = this.parseCommandArguments(command); + if (args.length <= 0) return; // Log an error? + + switch(args[0].toLowerCase()) { + case "automate": + this.executeAutomateConsoleCommand(args); + break; + case "clear": + case "cls": + this.clearConsole(); + break; + case "help": + this.executeHelpConsoleCommand(args); + break; + case "log": + this.executeLogConsoleCommand(args); + break; + case "skill": + this.executeSkillConsoleCommand(args); + break; + case "start": + this.executeStartConsoleCommand(player, args); + break; + case "stop": + this.resetAction(); + break; + default: + this.postToConsole("Invalid console command"); + break; + } + } + + triggerMigration(sourceCityName: string): void { + let destCityName = BladeburnerConstants.CityNames[getRandomInt(0, 5)]; + while (destCityName === sourceCityName) { + destCityName = BladeburnerConstants.CityNames[getRandomInt(0, 5)]; + } + const destCity = this.cities[destCityName]; + const sourceCity = this.cities[sourceCityName]; + if (destCity == null || sourceCity == null) { + throw new Error("Failed to find City with name: " + destCityName); + } + const rand = Math.random(); + let percentage = getRandomInt(3, 15) / 100; + + if (rand < 0.05 && sourceCity.comms > 0) { // 5% chance for community migration + percentage *= getRandomInt(2, 4); // Migration increases population change + --sourceCity.comms; + ++destCity.comms; + } + const count = Math.round(sourceCity.pop * percentage); + sourceCity.pop -= count; + destCity.pop += count; + } + + triggerPotentialMigration(sourceCityName: string, chance: number): void { + if (chance == null || isNaN(chance)) { + console.error("Invalid 'chance' parameter passed into Bladeburner.triggerPotentialMigration()"); + } + if (chance > 1) {chance /= 100;} + if (Math.random() < chance) {this.triggerMigration(sourceCityName);} + } + + randomEvent(): void { + const chance = Math.random(); + + // Choose random source/destination city for events + const sourceCityName = BladeburnerConstants.CityNames[getRandomInt(0, 5)]; + const sourceCity = this.cities[sourceCityName]; + if (!(sourceCity instanceof City)) { + throw new Error("sourceCity was not a City object in Bladeburner.randomEvent()"); + } + + let destCityName = BladeburnerConstants.CityNames[getRandomInt(0, 5)]; + while (destCityName === sourceCityName) { + destCityName = BladeburnerConstants.CityNames[getRandomInt(0, 5)]; + } + const destCity = this.cities[destCityName]; + + if (!(sourceCity instanceof City) || !(destCity instanceof City)) { + throw new Error("sourceCity/destCity was not a City object in Bladeburner.randomEvent()"); + } + + if (chance <= 0.05) { + // New Synthoid Community, 5% + ++sourceCity.comms; + const percentage = getRandomInt(10, 20) / 100; + const count = Math.round(sourceCity.pop * percentage); + sourceCity.pop += count; + if (this.logging.events) { + this.log("Intelligence indicates that a new Synthoid community was formed in a city"); + } + } else if (chance <= 0.1) { + // Synthoid Community Migration, 5% + if (sourceCity.comms <= 0) { + // If no comms in source city, then instead trigger a new Synthoid community event + ++sourceCity.comms; + const percentage = getRandomInt(10, 20) / 100; + const count = Math.round(sourceCity.pop * percentage); + sourceCity.pop += count; + if (this.logging.events) { + this.log("Intelligence indicates that a new Synthoid community was formed in a city"); + } + } else { + --sourceCity.comms; + ++destCity.comms; + + // Change pop + const percentage = getRandomInt(10, 20) / 100; + const count = Math.round(sourceCity.pop * percentage); + sourceCity.pop -= count; + destCity.pop += count; + + if (this.logging.events) { + this.log("Intelligence indicates that a Synthoid community migrated from " + sourceCityName + " to some other city"); + } + } + } else if (chance <= 0.3) { + // New Synthoids (non community), 20% + const percentage = getRandomInt(8, 24) / 100; + const count = Math.round(sourceCity.pop * percentage); + sourceCity.pop += count; + if (this.logging.events) { + this.log("Intelligence indicates that the Synthoid population of " + sourceCityName + " just changed significantly"); + } + } else if (chance <= 0.5) { + // Synthoid migration (non community) 20% + this.triggerMigration(sourceCityName); + if (this.logging.events) { + this.log("Intelligence indicates that a large number of Synthoids migrated from " + sourceCityName + " to some other city"); + } + } else if (chance <= 0.7) { + // Synthoid Riots (+chaos), 20% + sourceCity.chaos += 1; + sourceCity.chaos *= (1 + getRandomInt(5, 20) / 100); + if (this.logging.events) { + this.log("Tensions between Synthoids and humans lead to riots in " + sourceCityName + "! Chaos increased"); + } + } else if (chance <= 0.9) { + // Less Synthoids, 20% + const percentage = getRandomInt(8, 20) / 100; + const count = Math.round(sourceCity.pop * percentage); + sourceCity.pop -= count; + if (this.logging.events) { + this.log("Intelligence indicates that the Synthoid population of " + sourceCityName + " just changed significantly"); + } + } + // 10% chance of nothing happening + } + + /** + * Process stat gains from Contracts, Operations, and Black Operations + * @param action(Action obj) - Derived action class + * @param success(bool) - Whether action was successful + */ + gainActionStats(player: IPlayer, action: IAction, success: boolean): void { + const difficulty = action.getDifficulty(); + + /** + * Gain multiplier based on difficulty. If it changes then the + * same variable calculated in completeAction() needs to change too + */ + const difficultyMult = Math.pow(difficulty, BladeburnerConstants.DiffMultExponentialFactor) + difficulty / BladeburnerConstants.DiffMultLinearFactor; + + const time = this.actionTimeToComplete; + const successMult = success ? 1 : 0.5; + + const unweightedGain = time * BladeburnerConstants.BaseStatGain * successMult * difficultyMult; + const unweightedIntGain = time * BladeburnerConstants.BaseIntGain * successMult * difficultyMult; + const skillMult = this.skillMultipliers.expGain; + player.gainHackingExp(unweightedGain * action.weights.hack * player.hacking_exp_mult * skillMult); + player.gainStrengthExp(unweightedGain * action.weights.str * player.strength_exp_mult * skillMult); + player.gainDefenseExp(unweightedGain * action.weights.def * player.defense_exp_mult * skillMult); + player.gainDexterityExp(unweightedGain * action.weights.dex * player.dexterity_exp_mult * skillMult); + player.gainAgilityExp(unweightedGain * action.weights.agi * player.agility_exp_mult * skillMult); + player.gainCharismaExp(unweightedGain * action.weights.cha * player.charisma_exp_mult * skillMult); + let intExp = unweightedIntGain * action.weights.int * skillMult; + if (intExp > 1) { + intExp = Math.pow(intExp, 0.8); + } + player.gainIntelligenceExp(intExp); + } + + getDiplomacyEffectiveness(player: IPlayer): number { + // Returns a decimal by which the city's chaos level should be multiplied (e.g. 0.98) + const CharismaLinearFactor = 1e3; + const CharismaExponentialFactor = 0.045; + + const charismaEff = Math.pow(player.charisma, CharismaExponentialFactor) + player.charisma / CharismaLinearFactor; + return (100 - charismaEff) / 100; + } + + getRecruitmentSuccessChance(player: IPlayer): number { + return Math.pow(player.charisma, 0.45) / (this.teamSize + 1); + } + + getRecruitmentTime(player: IPlayer): number { + const effCharisma = player.charisma * this.skillMultipliers.effCha; + const charismaFactor = Math.pow(effCharisma, 0.81) + effCharisma / 90; + return Math.max(10, Math.round(BladeburnerConstants.BaseRecruitmentTimeNeeded - charismaFactor)); + } + + resetSkillMultipliers(): void { + this.skillMultipliers = { + successChanceAll: 1, + successChanceStealth: 1, + successChanceKill: 1, + successChanceContract: 1, + successChanceOperation: 1, + successChanceEstimate: 1, + actionTime: 1, + effHack: 1, + effStr: 1, + effDef: 1, + effDex: 1, + effAgi: 1, + effCha: 1, + effInt: 1, + stamina: 1, + money: 1, + expGain: 1, + }; + } + + updateSkillMultipliers(): void { + this.resetSkillMultipliers(); + for (const skillName in this.skills) { + if (this.skills.hasOwnProperty(skillName)) { + const skill = Skills[skillName]; + if (skill == null) { + throw new Error("Could not find Skill Object for: " + skillName); + } + const level = this.skills[skillName]; + if (level == null || level <= 0) {continue;} //Not upgraded + + const multiplierNames = Object.keys(this.skillMultipliers); + for (let i = 0; i < multiplierNames.length; ++i) { + const multiplierName = multiplierNames[i]; + if (skill.getMultiplier(multiplierName) != null && !isNaN(skill.getMultiplier(multiplierName))) { + const value = skill.getMultiplier(multiplierName) * level; + let multiplierValue = 1 + (value / 100); + if (multiplierName === "actionTime") { + multiplierValue = 1 - (value / 100); + } + this.skillMultipliers[multiplierName] *= multiplierValue; + } + } + } + } + } + + completeOperation(success: boolean): void { + if (this.action.type !== ActionTypes.Operation) { + throw new Error("completeOperation() called even though current action is not an Operation"); + } + const action = this.getActionObject(this.action); + if (action == null) { + throw new Error("Failed to get Contract/Operation Object for: " + this.action.name); + } + + // Calculate team losses + const teamCount = action.teamCount; + if (teamCount >= 1) { + let max; + if (success) { + max = Math.ceil(teamCount/2); + } else { + max = Math.floor(teamCount) + } + const losses = getRandomInt(0, max); + this.teamSize -= losses; + this.teamLost += losses; + if (this.logging.ops && losses > 0) { + this.log("Lost " + formatNumber(losses, 0) + " team members during this " + action.name); + } + } + + const city = this.getCurrentCity(); + switch (action.name) { + case "Investigation": + if (success) { + city.improvePopulationEstimateByPercentage(0.4 * this.skillMultipliers.successChanceEstimate); + if (Math.random() < (0.02 * this.skillMultipliers.successChanceEstimate)) { + city.improveCommunityEstimate(1); + } + } else { + this.triggerPotentialMigration(this.city, 0.1); + } + break; + case "Undercover Operation": + if (success) { + city.improvePopulationEstimateByPercentage(0.8 * this.skillMultipliers.successChanceEstimate); + if (Math.random() < (0.02 * this.skillMultipliers.successChanceEstimate)) { + city.improveCommunityEstimate(1); + } + } else { + this.triggerPotentialMigration(this.city, 0.15); + } + break; + case "Sting Operation": + if (success) { + city.changePopulationByPercentage(-0.1, {changeEstEqually:true, nonZero:true}); + } + city.changeChaosByCount(0.1); + break; + case "Raid": + if (success) { + city.changePopulationByPercentage(-1, {changeEstEqually:true, nonZero:true}); + --city.comms; + --city.commsEst; + } else { + const change = getRandomInt(-10, -5) / 10; + city.changePopulationByPercentage(change, {nonZero:true, changeEstEqually:false}); + } + city.changeChaosByPercentage(getRandomInt(1, 5)); + break; + case "Stealth Retirement Operation": + if (success) { + city.changePopulationByPercentage(-0.5, {changeEstEqually:true,nonZero:true}); + } + city.changeChaosByPercentage(getRandomInt(-3, -1)); + break; + case "Assassination": + if (success) { + city.changePopulationByCount(-1, {estChange:-1, estOffset: 0}); + } + city.changeChaosByPercentage(getRandomInt(-5, 5)); + break; + default: + throw new Error("Invalid Action name in completeOperation: " + this.action.name); + } + } + + getActionObject(actionId: IActionIdentifier): IAction | null { + /** + * Given an ActionIdentifier object, returns the corresponding + * GeneralAction, Contract, Operation, or BlackOperation object + */ + switch (actionId.type) { + case ActionTypes["Contract"]: + return this.contracts[actionId.name]; + case ActionTypes["Operation"]: + return this.operations[actionId.name]; + case ActionTypes["BlackOp"]: + case ActionTypes["BlackOperation"]: + return BlackOperations[actionId.name]; + case ActionTypes["Training"]: + return GeneralActions["Training"]; + case ActionTypes["Field Analysis"]: + return GeneralActions["Field Analysis"]; + case ActionTypes["Recruitment"]: + return GeneralActions["Recruitment"]; + case ActionTypes["Diplomacy"]: + return GeneralActions["Diplomacy"]; + case ActionTypes["Hyperbolic Regeneration Chamber"]: + return GeneralActions["Hyperbolic Regeneration Chamber"]; + default: + return null; + } + } + + completeContract(success: boolean): void { + if (this.action.type !== ActionTypes.Contract) { + throw new Error("completeContract() called even though current action is not a Contract"); + } + var city = this.getCurrentCity(); + if (success) { + switch (this.action.name) { + case "Tracking": + // Increase estimate accuracy by a relatively small amount + city.improvePopulationEstimateByCount(getRandomInt(100, 1e3)); + break; + case "Bounty Hunter": + city.changePopulationByCount(-1, {estChange:-1, estOffset: 0}); + city.changeChaosByCount(0.02); + break; + case "Retirement": + city.changePopulationByCount(-1, {estChange:-1, estOffset: 0}); + city.changeChaosByCount(0.04); + break; + default: + throw new Error("Invalid Action name in completeContract: " + this.action.name); + } + } + } + + completeAction(player: IPlayer): void { + switch (this.action.type) { + case ActionTypes["Contract"]: + case ActionTypes["Operation"]: { + try { + const isOperation = (this.action.type === ActionTypes["Operation"]); + const action = this.getActionObject(this.action); + if (action == null) { + throw new Error("Failed to get Contract/Operation Object for: " + this.action.name); + } + const difficulty = action.getDifficulty(); + const difficultyMultiplier = Math.pow(difficulty, BladeburnerConstants.DiffMultExponentialFactor) + difficulty / BladeburnerConstants.DiffMultLinearFactor; + const rewardMultiplier = Math.pow(action.rewardFac, action.level-1); + + // Stamina loss is based on difficulty + this.stamina -= (BladeburnerConstants.BaseStaminaLoss * difficultyMultiplier); + if (this.stamina < 0) {this.stamina = 0;} + + // Process Contract/Operation success/failure + if (action.attempt(this)) { + this.gainActionStats(player, action, true); + ++action.successes; + --action.count; + + // Earn money for contracts + let moneyGain = 0; + if (!isOperation) { + moneyGain = BladeburnerConstants.ContractBaseMoneyGain * rewardMultiplier * this.skillMultipliers.money; + player.gainMoney(moneyGain); + player.recordMoneySource(moneyGain, "bladeburner"); + } + + if (isOperation) { + action.setMaxLevel(BladeburnerConstants.OperationSuccessesPerLevel); + } else { + action.setMaxLevel(BladeburnerConstants.ContractSuccessesPerLevel); + } + if (action.rankGain) { + const gain = addOffset(action.rankGain * rewardMultiplier * BitNodeMultipliers.BladeburnerRank, 10); + this.changeRank(player, gain); + if (isOperation && this.logging.ops) { + this.log(action.name + " successfully completed! Gained " + formatNumber(gain, 3) + " rank"); + } else if (!isOperation && this.logging.contracts) { + this.log(action.name + " contract successfully completed! Gained " + formatNumber(gain, 3) + " rank and " + numeralWrapper.formatMoney(moneyGain)); + } + } + isOperation ? this.completeOperation(true) : this.completeContract(true); + } else { + this.gainActionStats(player, action, false); + ++action.failures; + let loss = 0, damage = 0; + if (action.rankLoss) { + loss = addOffset(action.rankLoss * rewardMultiplier, 10); + this.changeRank(player, -1 * loss); + } + if (action.hpLoss) { + damage = action.hpLoss * difficultyMultiplier; + damage = Math.ceil(addOffset(damage, 10)); + this.hpLost += damage; + const cost = calculateHospitalizationCost(player, damage); + if (player.takeDamage(damage)) { + ++this.numHosp; + this.moneyLost += cost; + } + } + let logLossText = ""; + if (loss > 0) {logLossText += "Lost " + formatNumber(loss, 3) + " rank. ";} + if (damage > 0) {logLossText += "Took " + formatNumber(damage, 0) + " damage.";} + if (isOperation && this.logging.ops) { + this.log(action.name + " failed! " + logLossText); + } else if (!isOperation && this.logging.contracts) { + this.log(action.name + " contract failed! " + logLossText); + } + isOperation ? this.completeOperation(false) : this.completeContract(false); + } + if (action.autoLevel) {action.level = action.maxLevel;} // Autolevel + this.startAction(player, this.action); // Repeat action + } catch(e) { + exceptionAlert(e); + } + break; + } + case ActionTypes["BlackOp"]: + case ActionTypes["BlackOperation"]: { + try { + const action = this.getActionObject(this.action); + if (action == null || !(action instanceof BlackOperation)) { + throw new Error("Failed to get BlackOperation Object for: " + this.action.name); + } + const difficulty = action.getDifficulty(); + const difficultyMultiplier = Math.pow(difficulty, BladeburnerConstants.DiffMultExponentialFactor) + difficulty / BladeburnerConstants.DiffMultLinearFactor; + + // Stamina loss is based on difficulty + this.stamina -= (BladeburnerConstants.BaseStaminaLoss * difficultyMultiplier); + if (this.stamina < 0) {this.stamina = 0;} + + // Team loss variables + const teamCount = action.teamCount; + let teamLossMax; + + if (action.attempt(this)) { + this.gainActionStats(player, action, true); + action.count = 0; + this.blackops[action.name] = true; + let rankGain = 0; + if (action.rankGain) { + rankGain = addOffset(action.rankGain * BitNodeMultipliers.BladeburnerRank, 10); + this.changeRank(player, rankGain); + } + teamLossMax = Math.ceil(teamCount/2); + + // Operation Daedalus + if (action.name === "Operation Daedalus") { + this.resetAction(); + return hackWorldDaemon(player.bitNodeN); + } + + if (this.logging.blackops) { + this.log(action.name + " successful! Gained " + formatNumber(rankGain, 1) + " rank"); + } + } else { + this.gainActionStats(player, action, false); + let rankLoss = 0; + let damage = 0; + if (action.rankLoss) { + rankLoss = addOffset(action.rankLoss, 10); + this.changeRank(player, -1 * rankLoss); + } + if (action.hpLoss) { + damage = action.hpLoss * difficultyMultiplier; + damage = Math.ceil(addOffset(damage, 10)); + const cost = calculateHospitalizationCost(player, damage); + if (player.takeDamage(damage)) { + ++this.numHosp; + this.moneyLost += cost; + } + } + teamLossMax = Math.floor(teamCount); + + if (this.logging.blackops) { + this.log(action.name + " failed! Lost " + formatNumber(rankLoss, 1) + " rank and took " + formatNumber(damage, 0) + " damage"); + } + } + + this.resetAction(); // Stop regardless of success or fail + + // Calculate team lossses + if (teamCount >= 1) { + const losses = getRandomInt(1, teamLossMax); + this.teamSize -= losses; + this.teamLost += losses; + if (this.logging.blackops) { + this.log("You lost " + formatNumber(losses, 0) + " team members during " + action.name); + } + } + } catch(e) { + exceptionAlert(e); + } + break; + } + case ActionTypes["Training"]: { + this.stamina -= (0.5 * BladeburnerConstants.BaseStaminaLoss); + const strExpGain = 30 * player.strength_exp_mult, + defExpGain = 30 * player.defense_exp_mult, + dexExpGain = 30 * player.dexterity_exp_mult, + agiExpGain = 30 * player.agility_exp_mult, + staminaGain = 0.04 * this.skillMultipliers.stamina; + player.gainStrengthExp(strExpGain); + player.gainDefenseExp(defExpGain); + player.gainDexterityExp(dexExpGain); + player.gainAgilityExp(agiExpGain); + this.staminaBonus += (staminaGain); + if (this.logging.general) { + this.log("Training completed. Gained: " + + formatNumber(strExpGain, 1) + " str exp, " + + formatNumber(defExpGain, 1) + " def exp, " + + formatNumber(dexExpGain, 1) + " dex exp, " + + formatNumber(agiExpGain, 1) + " agi exp, " + + formatNumber(staminaGain, 3) + " max stamina"); + } + this.startAction(player, this.action); // Repeat action + break; + } + case ActionTypes["FieldAnalysis"]: + case ActionTypes["Field Analysis"]: { + // Does not use stamina. Effectiveness depends on hacking, int, and cha + let eff = 0.04 * Math.pow(player.hacking_skill, 0.3) + + 0.04 * Math.pow(player.intelligence, 0.9) + + 0.02 * Math.pow(player.charisma, 0.3); + eff *= player.bladeburner_analysis_mult; + if (isNaN(eff) || eff < 0) { + throw new Error("Field Analysis Effectiveness calculated to be NaN or negative"); + } + const hackingExpGain = 20 * player.hacking_exp_mult, + charismaExpGain = 20 * player.charisma_exp_mult; + player.gainHackingExp(hackingExpGain); + player.gainIntelligenceExp(BladeburnerConstants.BaseIntGain); + player.gainCharismaExp(charismaExpGain); + this.changeRank(player, 0.1 * BitNodeMultipliers.BladeburnerRank); + this.getCurrentCity().improvePopulationEstimateByPercentage(eff * this.skillMultipliers.successChanceEstimate); + if (this.logging.general) { + this.log("Field analysis completed. Gained 0.1 rank, " + formatNumber(hackingExpGain, 1) + " hacking exp, and " + formatNumber(charismaExpGain, 1) + " charisma exp"); + } + this.startAction(player, this.action); // Repeat action + break; + } + case ActionTypes["Recruitment"]: { + const successChance = this.getRecruitmentSuccessChance(player); + if (Math.random() < successChance) { + const expGain = 2 * BladeburnerConstants.BaseStatGain * this.actionTimeToComplete; + player.gainCharismaExp(expGain); + ++this.teamSize; + if (this.logging.general) { + this.log("Successfully recruited a team member! Gained " + formatNumber(expGain, 1) + " charisma exp"); + } + } else { + const expGain = BladeburnerConstants.BaseStatGain * this.actionTimeToComplete; + player.gainCharismaExp(expGain); + if (this.logging.general) { + this.log("Failed to recruit a team member. Gained " + formatNumber(expGain, 1) + " charisma exp"); + } + } + this.startAction(player, this.action); // Repeat action + break; + } + case ActionTypes["Diplomacy"]: { + let eff = this.getDiplomacyEffectiveness(player); + this.getCurrentCity().chaos *= eff; + if (this.getCurrentCity().chaos < 0) { this.getCurrentCity().chaos = 0; } + if (this.logging.general) { + this.log(`Diplomacy completed. Chaos levels in the current city fell by ${numeralWrapper.formatPercentage(1 - eff)}`); + } + this.startAction(player, this.action); // Repeat Action + break; + } + case ActionTypes["Hyperbolic Regeneration Chamber"]: { + player.regenerateHp(BladeburnerConstants.HrcHpGain); + + const staminaGain = this.maxStamina * (BladeburnerConstants.HrcStaminaGain / 100); + this.stamina = Math.min(this.maxStamina, this.stamina + staminaGain); + this.startAction(player, this.action); + if (this.logging.general) { + this.log(`Rested in Hyperbolic Regeneration Chamber. Restored ${BladeburnerConstants.HrcHpGain} HP and gained ${numeralWrapper.formatStamina(staminaGain)} stamina`); + } + break; + } + default: + console.error(`Bladeburner.completeAction() called for invalid action: ${this.action.type}`); + break; + } + } + + changeRank(player: IPlayer, change: number): void { + if (isNaN(change)) {throw new Error("NaN passed into Bladeburner.changeRank()");} + this.rank += change; + if (this.rank < 0) {this.rank = 0;} + this.maxRank = Math.max(this.rank, this.maxRank); + + const bladeburnersFactionName = "Bladeburners"; + if (factionExists(bladeburnersFactionName)) { + const bladeburnerFac = Factions[bladeburnersFactionName]; + if (!(bladeburnerFac instanceof Faction)) { + throw new Error("Could not properly get Bladeburner Faction object in Bladeburner UI Overview Faction button"); + } + if (bladeburnerFac.isMember) { + const favorBonus = 1 + (bladeburnerFac.favor / 100); + bladeburnerFac.playerReputation += (BladeburnerConstants.RankToFactionRepFactor * change * player.faction_rep_mult * favorBonus); + } + } + + // Gain skill points + const rankNeededForSp = (this.totalSkillPoints+1) * BladeburnerConstants.RanksPerSkillPoint; + if (this.maxRank >= rankNeededForSp) { + // Calculate how many skill points to gain + const gainedSkillPoints = Math.floor((this.maxRank - rankNeededForSp) / BladeburnerConstants.RanksPerSkillPoint + 1); + this.skillPoints += gainedSkillPoints; + this.totalSkillPoints += gainedSkillPoints; + } + } + + processAction(player: IPlayer, seconds: number): void { + if (this.action.type === ActionTypes["Idle"]) return; + if (this.actionTimeToComplete <= 0) { + throw new Error(`Invalid actionTimeToComplete value: ${this.actionTimeToComplete}, type; ${this.action.type}`); + } + if (!(this.action instanceof ActionIdentifier)) { + throw new Error("Bladeburner.action is not an ActionIdentifier Object"); + } + + // If the previous action went past its completion time, add to the next action + // This is not added inmediatly in case the automation changes the action + this.actionTimeCurrent += seconds + this.actionTimeOverflow; + this.actionTimeOverflow = 0; + if (this.actionTimeCurrent >= this.actionTimeToComplete) { + this.actionTimeOverflow = this.actionTimeCurrent - this.actionTimeToComplete; + return this.completeAction(player); + } + } + + calculateStaminaGainPerSecond(player: IPlayer): number { + const effAgility = player.agility * this.skillMultipliers.effAgi; + const maxStaminaBonus = this.maxStamina / BladeburnerConstants.MaxStaminaToGainFactor; + const gain = (BladeburnerConstants.StaminaGainPerSecond + maxStaminaBonus) * Math.pow(effAgility, 0.17); + return gain * (this.skillMultipliers.stamina * player.bladeburner_stamina_gain_mult); + } + + calculateMaxStamina(player: IPlayer) { + const effAgility = player.agility * this.skillMultipliers.effAgi; + let maxStamina = (Math.pow(effAgility, 0.8) + this.staminaBonus) * + this.skillMultipliers.stamina * + player.bladeburner_max_stamina_mult; + if (this.maxStamina !== maxStamina) { + const oldMax = this.maxStamina; + this.maxStamina = maxStamina; + this.stamina = this.maxStamina * this.stamina / oldMax; + } + if (isNaN(maxStamina)) {throw new Error("Max Stamina calculated to be NaN in Bladeburner.calculateMaxStamina()");} + } + + create(): void { + this.contracts["Tracking"] = new Contract({ + name:"Tracking", + desc:"Identify and locate Synthoids. This contract involves reconnaissance " + + "and information-gathering ONLY. Do NOT engage. Stealth is of the utmost importance.

        " + + "Successfully completing Tracking contracts will slightly improve your Synthoid population estimate for " + + "whatever city you are currently in.", + baseDifficulty:125,difficultyFac:1.02,rewardFac:1.041, + rankGain:0.3, hpLoss:0.5, + count:getRandomInt(25, 150), countGrowth:getRandomInt(5, 75)/10, + weights:{hack:0,str:0.05,def:0.05,dex:0.35,agi:0.35,cha:0.1, int:0.05}, + decays:{hack:0,str:0.91,def:0.91,dex:0.91,agi:0.91,cha:0.9, int:1}, + isStealth:true, + }); + this.contracts["Bounty Hunter"] = new Contract({ + name:"Bounty Hunter", + desc:"Hunt down and capture fugitive Synthoids. These Synthoids are wanted alive.

        " + + "Successfully completing a Bounty Hunter contract will lower the population in your " + + "current city, and will also increase its chaos level.", + baseDifficulty:250, difficultyFac:1.04,rewardFac:1.085, + rankGain:0.9, hpLoss:1, + count:getRandomInt(5, 150), countGrowth:getRandomInt(5, 75)/10, + weights:{hack:0,str:0.15,def:0.15,dex:0.25,agi:0.25,cha:0.1, int:0.1}, + decays:{hack:0,str:0.91,def:0.91,dex:0.91,agi:0.91,cha:0.8, int:0.9}, + isKill:true, + }); + this.contracts["Retirement"] = new Contract({ + name:"Retirement", + desc:"Hunt down and retire (kill) rogue Synthoids.

        " + + "Successfully completing a Retirement contract will lower the population in your current " + + "city, and will also increase its chaos level.", + baseDifficulty:200, difficultyFac:1.03, rewardFac:1.065, + rankGain:0.6, hpLoss:1, + count:getRandomInt(5, 150), countGrowth:getRandomInt(5, 75)/10, + weights:{hack:0,str:0.2,def:0.2,dex:0.2,agi:0.2,cha:0.1, int:0.1}, + decays:{hack:0,str:0.91,def:0.91,dex:0.91,agi:0.91,cha:0.8, int:0.9}, + isKill:true, + }); + + this.operations["Investigation"] = new Operation({ + name:"Investigation", + desc:"As a field agent, investigate and identify Synthoid " + + "populations, movements, and operations.

        Successful " + + "Investigation ops will increase the accuracy of your " + + "synthoid data.

        " + + "You will NOT lose HP from failed Investigation ops.", + baseDifficulty:400, difficultyFac:1.03,rewardFac:1.07,reqdRank:25, + rankGain:2.2, rankLoss:0.2, + count:getRandomInt(1, 100), countGrowth:getRandomInt(10, 40)/10, + weights:{hack:0.25,str:0.05,def:0.05,dex:0.2,agi:0.1,cha:0.25, int:0.1}, + decays:{hack:0.85,str:0.9,def:0.9,dex:0.9,agi:0.9,cha:0.7, int:0.9}, + isStealth:true, + }); + this.operations["Undercover Operation"] = new Operation({ + name:"Undercover Operation", + desc:"Conduct undercover operations to identify hidden " + + "and underground Synthoid communities and organizations.

        " + + "Successful Undercover ops will increase the accuracy of your synthoid " + + "data.", + baseDifficulty:500, difficultyFac:1.04, rewardFac:1.09, reqdRank:100, + rankGain:4.4, rankLoss:0.4, hpLoss:2, + count:getRandomInt(1, 100), countGrowth:getRandomInt(10, 40)/10, + weights:{hack:0.2,str:0.05,def:0.05,dex:0.2,agi:0.2,cha:0.2, int:0.1}, + decays:{hack:0.8,str:0.9,def:0.9,dex:0.9,agi:0.9,cha:0.7, int:0.9}, + isStealth:true, + }); + this.operations["Sting Operation"] = new Operation({ + name:"Sting Operation", + desc:"Conduct a sting operation to bait and capture particularly " + + "notorious Synthoid criminals.", + baseDifficulty:650, difficultyFac:1.04, rewardFac:1.095, reqdRank:500, + rankGain:5.5, rankLoss:0.5, hpLoss:2.5, + count:getRandomInt(1, 150), countGrowth:getRandomInt(3, 40)/10, + weights:{hack:0.25,str:0.05,def:0.05,dex:0.25,agi:0.1,cha:0.2, int:0.1}, + decays:{hack:0.8,str:0.85,def:0.85,dex:0.85,agi:0.85,cha:0.7, int:0.9}, + isStealth:true, + }); + this.operations["Raid"] = new Operation({ + name:"Raid", + desc:"Lead an assault on a known Synthoid community. Note that " + + "there must be an existing Synthoid community in your current city " + + "in order for this Operation to be successful.", + baseDifficulty:800, difficultyFac:1.045, rewardFac:1.1, reqdRank:3000, + rankGain:55,rankLoss:2.5,hpLoss:50, + count:getRandomInt(1, 150), countGrowth:getRandomInt(2, 40)/10, + weights:{hack:0.1,str:0.2,def:0.2,dex:0.2,agi:0.2,cha:0, int:0.1}, + decays:{hack:0.7,str:0.8,def:0.8,dex:0.8,agi:0.8,cha:0, int:0.9}, + isKill:true, + }); + this.operations["Stealth Retirement Operation"] = new Operation({ + name:"Stealth Retirement Operation", + desc:"Lead a covert operation to retire Synthoids. The " + + "objective is to complete the task without " + + "drawing any attention. Stealth and discretion are key.", + baseDifficulty:1000, difficultyFac:1.05, rewardFac:1.11, reqdRank:20e3, + rankGain:22, rankLoss:2, hpLoss:10, + count:getRandomInt(1, 150), countGrowth:getRandomInt(1, 20)/10, + weights:{hack:0.1,str:0.1,def:0.1,dex:0.3,agi:0.3,cha:0, int:0.1}, + decays:{hack:0.7,str:0.8,def:0.8,dex:0.8,agi:0.8,cha:0, int:0.9}, + isStealth:true, isKill:true, + }); + this.operations["Assassination"] = new Operation({ + name:"Assassination", + desc:"Assassinate Synthoids that have been identified as " + + "important, high-profile social and political leaders " + + "in the Synthoid communities.", + baseDifficulty:1500, difficultyFac:1.06, rewardFac:1.14, reqdRank:50e3, + rankGain:44, rankLoss:4, hpLoss:5, + count:getRandomInt(1, 150), countGrowth:getRandomInt(1, 20)/10, + weights:{hack:0.1,str:0.1,def:0.1,dex:0.3,agi:0.3,cha:0, int:0.1}, + decays:{hack:0.6,str:0.8,def:0.8,dex:0.8,agi:0.8,cha:0, int:0.8}, + isStealth:true, isKill:true, + }); + } + + process(player: IPlayer): void { + // Edge case condition...if Operation Daedalus is complete trigger the BitNode + if (redPillFlag === false && this.blackops.hasOwnProperty("Operation Daedalus")) { + return hackWorldDaemon(player.bitNodeN); + } + + // If the Player starts doing some other actions, set action to idle and alert + if (Augmentations[AugmentationNames.BladesSimulacrum].owned === false && player.isWorking) { + if (this.action.type !== ActionTypes["Idle"]) { + let msg = "Your Bladeburner action was cancelled because you started doing something else."; + if (this.automateEnabled) { + msg += `

        Your automation was disabled as well. You will have to re-enable it through the Bladeburner console` + this.automateEnabled = false; + } + if (!Settings.SuppressBladeburnerPopup) { + dialogBoxCreate(msg); + } + } + this.resetAction(); + } + + // If the Player has no Stamina, set action to idle + if (this.stamina <= 0) { + this.log("Your Bladeburner action was cancelled because your stamina hit 0"); + this.resetAction(); + } + + // A 'tick' for this mechanic is one second (= 5 game cycles) + if (this.storedCycles >= BladeburnerConstants.CyclesPerSecond) { + let seconds = Math.floor(this.storedCycles / BladeburnerConstants.CyclesPerSecond); + seconds = Math.min(seconds, 5); // Max of 5 'ticks' + this.storedCycles -= seconds * BladeburnerConstants.CyclesPerSecond; + + // Stamina + this.calculateMaxStamina(player); + this.stamina += (this.calculateStaminaGainPerSecond(player) * seconds); + this.stamina = Math.min(this.maxStamina, this.stamina); + + // Count increase for contracts/operations + for (const contract of (Object.values(this.contracts) as Contract[])) { + contract.count += (seconds * contract.countGrowth/BladeburnerConstants.ActionCountGrowthPeriod); + } + for (const op of (Object.values(this.operations) as Operation[])) { + op.count += (seconds * op.countGrowth/BladeburnerConstants.ActionCountGrowthPeriod); + } + + // Chaos goes down very slowly + for (const cityName of BladeburnerConstants.CityNames) { + const city = this.cities[cityName]; + if (!(city instanceof City)) {throw new Error("Invalid City object when processing passive chaos reduction in Bladeburner.process");} + city.chaos -= (0.0001 * seconds); + city.chaos = Math.max(0, city.chaos); + } + + // Random Events + this.randomEventCounter -= seconds; + if (this.randomEventCounter <= 0) { + this.randomEvent(); + // Add instead of setting because we might have gone over the required time for the event + this.randomEventCounter += getRandomInt(240, 600); + } + + this.processAction(player, seconds); + + // Automation + if (this.automateEnabled) { + // Note: Do NOT set this.action = this.automateActionHigh/Low since it creates a reference + if (this.stamina <= this.automateThreshLow) { + if (this.action.name !== this.automateActionLow.name || this.action.type !== this.automateActionLow.type) { + this.action = new ActionIdentifier({type: this.automateActionLow.type, name: this.automateActionLow.name}); + this.startAction(player, this.action); + } + } else if (this.stamina >= this.automateThreshHigh) { + if (this.action.name !== this.automateActionHigh.name || this.action.type !== this.automateActionHigh.type) { + this.action = new ActionIdentifier({type: this.automateActionHigh.type, name: this.automateActionHigh.name}); + this.startAction(player, this.action); + } + } + } + } } getTypeAndNameFromActionId(actionId: IActionIdentifier): {type: string, name: string} { - return getTypeAndNameFromActionId(this, actionId); - } + const res = {type: '', name: ''}; + const types = Object.keys(ActionTypes); + for (let i = 0; i < types.length; ++i) { + if (actionId.type === ActionTypes[types[i]]) { + res.type = types[i]; + break; + } + } + if (res.type == null) {res.type = "Idle";} + res.name = actionId.name != null ? actionId.name : "Idle"; + return res; + } getContractNamesNetscriptFn(): string[] { - return getContractNamesNetscriptFn(this); + return Object.keys(this.contracts); } - getOperationNamesNetscriptFn(): string[] { - return getOperationNamesNetscriptFn(this); + return Object.keys(this.operations); } - getBlackOpNamesNetscriptFn(): string[] { - return getBlackOpNamesNetscriptFn(this); + return Object.keys(BlackOperations); } - getGeneralActionNamesNetscriptFn(): string[] { - return getGeneralActionNamesNetscriptFn(this); + return Object.keys(GeneralActions); } - getSkillNamesNetscriptFn(): string[] { - return getSkillNamesNetscriptFn(this); + return Object.keys(Skills); } + startActionNetscriptFn(player: IPlayer, type: string, name: string, workerScript: WorkerScript) { + const errorLogText = `Invalid action: type='${type}' name='${name}'`; + const actionId = this.getActionIdFromTypeAndName(type, name); + if (actionId == null) { + workerScript.log("bladeburner.startAction", errorLogText); + return false; + } - startActionNetscriptFn(player: IPlayer, type: string, name: string, workerScript: WorkerScript): boolean { - return startActionNetscriptFn(this, player, type, name, workerScript); + // Special logic for Black Ops + if (actionId.type === ActionTypes["BlackOp"]) { + // Can't start a BlackOp if you don't have the required rank + const action = this.getActionObject(actionId); + if(action == null) throw new Error('Action not found ${actionId.type}, ${actionId.name}'); + if(!(action instanceof BlackOperation)) throw new Error(`Action should be BlackOperation but isn't`); + const blackOp = (action as BlackOperation); + if (action.reqdRank > this.rank) { + workerScript.log("bladeburner.startAction", `Insufficient rank to start Black Op '${actionId.name}'.`); + return false; + } + + // Can't start a BlackOp if its already been done + if (this.blackops[actionId.name] != null) { + workerScript.log("bladeburner.startAction", `Black Op ${actionId.name} has already been completed.`); + return false; + } + + // Can't start a BlackOp if you haven't done the one before it + var blackops = []; + for (const nm in BlackOperations) { + if (BlackOperations.hasOwnProperty(nm)) { + blackops.push(nm); + } + } + blackops.sort(function(a, b) { + return (BlackOperations[a].reqdRank - BlackOperations[b].reqdRank); // Sort black ops in intended order + }); + + let i = blackops.indexOf(actionId.name); + if (i === -1) { + workerScript.log("bladeburner.startAction", `Invalid Black Op: '${name}'`); + return false; + } + + if (i > 0 && this.blackops[blackops[i-1]] == null) { + workerScript.log("bladeburner.startAction", `Preceding Black Op must be completed before starting '${actionId.name}'.`); + return false; + } + } + + try { + this.startAction(player, actionId); + workerScript.log("bladeburner.startAction", `Starting bladeburner action with type '${type}' and name ${name}"`); + return true; + } catch(e) { + this.resetAction(); + workerScript.log("bladeburner.startAction", errorLogText); + return false; + } } - getActionTimeNetscriptFn(player: IPlayer, type: string, name: string, workerScript: WorkerScript): number { - return getActionTimeNetscriptFn(this, player, type, name, workerScript); - } + const errorLogText = `Invalid action: type='${type}' name='${name}'` + const actionId = this.getActionIdFromTypeAndName(type, name); + if (actionId == null) { + workerScript.log("bladeburner.getActionTime", errorLogText); + return -1; + } + const actionObj = this.getActionObject(actionId); + if (actionObj == null) { + workerScript.log("bladeburner.getActionTime", errorLogText); + return -1; + } + + switch (actionId.type) { + case ActionTypes["Contract"]: + case ActionTypes["Operation"]: + case ActionTypes["BlackOp"]: + case ActionTypes["BlackOperation"]: + return actionObj.getActionTime(this); + case ActionTypes["Training"]: + case ActionTypes["Field Analysis"]: + case ActionTypes["FieldAnalysis"]: + return 30; + case ActionTypes["Recruitment"]: + return this.getRecruitmentTime(player); + case ActionTypes["Diplomacy"]: + case ActionTypes["Hyperbolic Regeneration Chamber"]: + return 60; + default: + workerScript.log("bladeburner.getActionTime", errorLogText); + return -1; + } + } getActionEstimatedSuccessChanceNetscriptFn(player: IPlayer, type: string, name: string, workerScript: WorkerScript): number { - return getActionEstimatedSuccessChanceNetscriptFn(this, player, type, name, workerScript); - } + const errorLogText = `Invalid action: type='${type}' name='${name}'` + const actionId = this.getActionIdFromTypeAndName(type, name); + if (actionId == null) { + workerScript.log("bladeburner.getActionEstimatedSuccessChance", errorLogText); + return -1; + } - getActionCountRemainingNetscriptFn(type: string, name: string, workerScript: WorkerScript): number { - return getActionCountRemainingNetscriptFn(this, type, name, workerScript); - } + const actionObj = this.getActionObject(actionId); + if (actionObj == null) { + workerScript.log("bladeburner.getActionEstimatedSuccessChance", errorLogText); + return -1; + } - getSkillLevelNetscriptFn(skillName: string, workerScript: WorkerScript): number { - return getSkillLevelNetscriptFn(this, skillName, workerScript); + switch (actionId.type) { + case ActionTypes["Contract"]: + case ActionTypes["Operation"]: + case ActionTypes["BlackOp"]: + case ActionTypes["BlackOperation"]: + return actionObj.getSuccessChance(this, {est:true}); + case ActionTypes["Training"]: + case ActionTypes["Field Analysis"]: + case ActionTypes["FieldAnalysis"]: + return 1; + case ActionTypes["Recruitment"]: + return this.getRecruitmentSuccessChance(player); + default: + workerScript.log("bladeburner.getActionEstimatedSuccessChance", errorLogText); + return -1; + } } + getActionCountRemainingNetscriptFn(type: string, name: string, workerScript: WorkerScript) { + const errorLogText = `Invalid action: type='${type}' name='${name}'`; + const actionId = this.getActionIdFromTypeAndName(type, name); + if (actionId == null) { + workerScript.log("bladeburner.getActionCountRemaining", errorLogText); + return -1; + } - getSkillUpgradeCostNetscriptFn(skillName: string, workerScript: WorkerScript): number { - return getSkillUpgradeCostNetscriptFn(this, skillName, workerScript); + const actionObj = this.getActionObject(actionId); + if (actionObj == null) { + workerScript.log("bladeburner.getActionCountRemaining", errorLogText); + return -1; + } + + switch (actionId.type) { + case ActionTypes["Contract"]: + case ActionTypes["Operation"]: + return Math.floor( actionObj.count ); + case ActionTypes["BlackOp"]: + case ActionTypes["BlackOperation"]: + if (this.blackops[name] != null) { + return 0; + } else { + return 1; + } + case ActionTypes["Training"]: + case ActionTypes["Field Analysis"]: + case ActionTypes["FieldAnalysis"]: + return Infinity; + default: + workerScript.log("bladeburner.getActionCountRemaining", errorLogText); + return -1; + } } + getSkillLevelNetscriptFn(skillName: string, workerScript: WorkerScript) { + if (skillName === "" || !Skills.hasOwnProperty(skillName)) { + workerScript.log("bladeburner.getSkillLevel", `Invalid skill: '${skillName}'`); + return -1; + } - upgradeSkillNetscriptFn(skillName: string, workerScript: WorkerScript): boolean { - return upgradeSkillNetscriptFn(this, skillName, workerScript); + if (this.skills[skillName] == null) { + return 0; + } else { + return this.skills[skillName]; + } } + getSkillUpgradeCostNetscriptFn(skillName: string, workerScript: WorkerScript) { + if (skillName === "" || !Skills.hasOwnProperty(skillName)) { + workerScript.log("bladeburner.getSkillUpgradeCost", `Invalid skill: '${skillName}'`); + return -1; + } + const skill = Skills[skillName]; + if (this.skills[skillName] == null) { + return skill.calculateCost(0); + } else { + return skill.calculateCost(this.skills[skillName]); + } + } + upgradeSkillNetscriptFn(skillName: string, workerScript: WorkerScript) { + const errorLogText = `Invalid skill: '${skillName}'`; + if (!Skills.hasOwnProperty(skillName)) { + workerScript.log("bladeburner.upgradeSkill", errorLogText); + return false; + } + + const skill = Skills[skillName]; + let currentLevel = 0; + if (this.skills[skillName] && !isNaN(this.skills[skillName])) { + currentLevel = this.skills[skillName]; + } + const cost = skill.calculateCost(currentLevel); + + if(skill.maxLvl && currentLevel >= skill.maxLvl) { + workerScript.log("bladeburner.upgradeSkill", `Skill '${skillName}' is already maxed.`); + return false; + } + + if (this.skillPoints < cost) { + workerScript.log("bladeburner.upgradeSkill", `You do not have enough skill points to upgrade ${skillName} (You have ${this.skillPoints}, you need ${cost})`); + return false; + } + + this.skillPoints -= cost; + this.upgradeSkill(skill); + workerScript.log("bladeburner.upgradeSkill", `'${skillName}' upgraded to level ${this.skills[skillName]}`); + return true; + } getTeamSizeNetscriptFn(type: string, name: string, workerScript: WorkerScript): number { - return getTeamSizeNetscriptFn(this, type, name, workerScript); - } + if (type === "" && name === "") { + return this.teamSize; + } + const errorLogText = `Invalid action: type='${type}' name='${name}'`; + const actionId = this.getActionIdFromTypeAndName(type, name); + if (actionId == null) { + workerScript.log("bladeburner.getTeamSize", errorLogText); + return -1; + } + + const actionObj = this.getActionObject(actionId); + if (actionObj == null) { + workerScript.log("bladeburner.getTeamSize", errorLogText); + return -1; + } + + if (actionId.type === ActionTypes["Operation"] || + actionId.type === ActionTypes["BlackOp"] || + actionId.type === ActionTypes["BlackOperation"]) { + return actionObj.teamCount; + } else { + return 0; + } + } setTeamSizeNetscriptFn(type: string, name: string, size: number, workerScript: WorkerScript): number { - return setTeamSizeNetscriptFn(this, type, name, size, workerScript); + const errorLogText = `Invalid action: type='${type}' name='${name}'`; + const actionId = this.getActionIdFromTypeAndName(type, name); + if (actionId == null) { + workerScript.log("bladeburner.setTeamSize", errorLogText); + return -1; + } + + if (actionId.type !== ActionTypes["Operation"] && + actionId.type !== ActionTypes["BlackOp"] && + actionId.type !== ActionTypes["BlackOperation"]) { + workerScript.log("bladeburner.setTeamSize", "Only valid for 'Operations' and 'BlackOps'"); + return -1; + } + + const actionObj = this.getActionObject(actionId); + if (actionObj == null) { + workerScript.log("bladeburner.setTeamSize", errorLogText); + return -1; + } + + let sanitizedSize = Math.round(size); + if (isNaN(sanitizedSize) || sanitizedSize < 0) { + workerScript.log("bladeburner.setTeamSize", `Invalid size: ${size}`); + return -1; + } + if (this.teamSize < sanitizedSize) {sanitizedSize = this.teamSize;} + actionObj.teamCount = sanitizedSize; + workerScript.log("bladeburner.setTeamSize", `Team size for '${name}' set to ${sanitizedSize}.`); + return sanitizedSize; + } + joinBladeburnerFactionNetscriptFn(workerScript: WorkerScript): boolean { + var bladeburnerFac = Factions["Bladeburners"]; + if (bladeburnerFac.isMember) { + return true; + } else if (this.rank >= BladeburnerConstants.RankNeededForFaction) { + joinFaction(bladeburnerFac); + workerScript.log("bladeburner.joinBladeburnerFaction", "Joined Bladeburners faction."); + return true; + } else { + workerScript.log("bladeburner.joinBladeburnerFaction", `You do not have the required rank (${this.rank}/${BladeburnerConstants.RankNeededForFaction}).`); + return false; + } } - joinBladeburnerFactionNetscriptFn(workerScript: WorkerScript): boolean { - return joinBladeburnerFactionNetscriptFn(this, workerScript); - } /** * Serialize the current object to a JSON save state. @@ -230,1882 +1983,4 @@ export class Bladeburner implements IBladeburner { } } -Reviver.constructors.Bladeburner = Bladeburner; - -export function getActionIdFromTypeAndName(bladeburner: IBladeburner, type: string = "", name: string = ""): IActionIdentifier | null { - if (type === "" || name === "") {return null;} - const action = new ActionIdentifier(); - const convertedType = type.toLowerCase().trim(); - const convertedName = name.toLowerCase().trim(); - switch (convertedType) { - case "contract": - case "contracts": - case "contr": - action.type = ActionTypes["Contract"]; - if (bladeburner.contracts.hasOwnProperty(name)) { - action.name = name; - return action; - } else { - return null; - } - break; - case "operation": - case "operations": - case "op": - case "ops": - action.type = ActionTypes["Operation"]; - if (bladeburner.operations.hasOwnProperty(name)) { - action.name = name; - return action; - } else { - return null; - } - break; - case "blackoperation": - case "black operation": - case "black operations": - case "black op": - case "black ops": - case "blackop": - case "blackops": - action.type = ActionTypes["BlackOp"]; - if (BlackOperations.hasOwnProperty(name)) { - action.name = name; - return action; - } else { - return null; - } - break; - case "general": - case "general action": - case "gen": - break; - default: - return null; - } - - if (convertedType.startsWith("gen")) { - switch (convertedName) { - case "training": - action.type = ActionTypes["Training"]; - action.name = "Training"; - break; - case "recruitment": - case "recruit": - action.type = ActionTypes["Recruitment"]; - action.name = "Recruitment"; - break; - case "field analysis": - case "fieldanalysis": - action.type = ActionTypes["Field Analysis"]; - action.name = "Field Analysis"; - break; - case "diplomacy": - action.type = ActionTypes["Diplomacy"]; - action.name = "Diplomacy"; - break; - case "hyperbolic regeneration chamber": - action.type = ActionTypes["Hyperbolic Regeneration Chamber"]; - action.name = "Hyperbolic Regeneration Chamber"; - break; - default: - return null; - } - return action; - } - - return null; -} - -export function executeStartConsoleCommand(bladeburner: IBladeburner, player: IPlayer, args: string[]): void { - if (args.length !== 3) { - bladeburner.postToConsole("Invalid usage of 'start' console command: start [type] [name]"); - bladeburner.postToConsole("Use 'help start' for more info"); - return; - } - const name = args[2]; - switch (args[1].toLowerCase()) { - case "general": - case "gen": - if (GeneralActions[name] != null) { - bladeburner.action.type = ActionTypes[name]; - bladeburner.action.name = name; - startAction(bladeburner, player, bladeburner.action); - } else { - bladeburner.postToConsole("Invalid action name specified: " + args[2]); - } - break; - case "contract": - case "contracts": - if (bladeburner.contracts[name] != null) { - bladeburner.action.type = ActionTypes.Contract; - bladeburner.action.name = name; - startAction(bladeburner, player, bladeburner.action); - } else { - bladeburner.postToConsole("Invalid contract name specified: " + args[2]); - } - break; - case "ops": - case "op": - case "operations": - case "operation": - if (bladeburner.operations[name] != null) { - bladeburner.action.type = ActionTypes.Operation; - bladeburner.action.name = name; - startAction(bladeburner,player, bladeburner.action); - } else { - bladeburner.postToConsole("Invalid Operation name specified: " + args[2]); - } - break; - case "blackops": - case "blackop": - case "black operations": - case "black operation": - if (BlackOperations[name] != null) { - bladeburner.action.type = ActionTypes.BlackOperation; - bladeburner.action.name = name; - startAction(bladeburner,player, bladeburner.action); - } else { - bladeburner.postToConsole("Invalid BlackOp name specified: " + args[2]); - } - break; - default: - bladeburner.postToConsole("Invalid action/event type specified: " + args[1]); - bladeburner.postToConsole("Examples of valid action/event identifiers are: [general, contract, op, blackop]"); - break; - } -} - -export function executeSkillConsoleCommand(bladeburner: IBladeburner, args: string[]): void { - switch (args.length) { - case 1: - // Display Skill Help Command - bladeburner.postToConsole("Invalid usage of 'skill' console command: skill [action] [name]"); - bladeburner.postToConsole("Use 'help skill' for more info"); - break; - case 2: - if (args[1].toLowerCase() === "list") { - // List all skills and their level - bladeburner.postToConsole("Skills: "); - const skillNames = Object.keys(Skills); - for(let i = 0; i < skillNames.length; ++i) { - let skill = Skills[skillNames[i]]; - let level = 0; - if (bladeburner.skills[skill.name] != null) {level = bladeburner.skills[skill.name];} - bladeburner.postToConsole(skill.name + ": Level " + formatNumber(level, 0)); - } - bladeburner.postToConsole(" "); - bladeburner.postToConsole("Effects: "); - const multKeys = Object.keys(bladeburner.skillMultipliers); - for (let i = 0; i < multKeys.length; ++i) { - let mult = bladeburner.skillMultipliers[multKeys[i]]; - if (mult && mult !== 1) { - mult = formatNumber(mult, 3); - switch(multKeys[i]) { - case "successChanceAll": - bladeburner.postToConsole("Total Success Chance: x" + mult); - break; - case "successChanceStealth": - bladeburner.postToConsole("Stealth Success Chance: x" + mult); - break; - case "successChanceKill": - bladeburner.postToConsole("Retirement Success Chance: x" + mult); - break; - case "successChanceContract": - bladeburner.postToConsole("Contract Success Chance: x" + mult); - break; - case "successChanceOperation": - bladeburner.postToConsole("Operation Success Chance: x" + mult); - break; - case "successChanceEstimate": - bladeburner.postToConsole("Synthoid Data Estimate: x" + mult); - break; - case "actionTime": - bladeburner.postToConsole("Action Time: x" + mult); - break; - case "effHack": - bladeburner.postToConsole("Hacking Skill: x" + mult); - break; - case "effStr": - bladeburner.postToConsole("Strength: x" + mult); - break; - case "effDef": - bladeburner.postToConsole("Defense: x" + mult); - break; - case "effDex": - bladeburner.postToConsole("Dexterity: x" + mult); - break; - case "effAgi": - bladeburner.postToConsole("Agility: x" + mult); - break; - case "effCha": - bladeburner.postToConsole("Charisma: x" + mult); - break; - case "effInt": - bladeburner.postToConsole("Intelligence: x" + mult); - break; - case "stamina": - bladeburner.postToConsole("Stamina: x" + mult); - break; - default: - console.warn(`Unrecognized SkillMult Key: ${multKeys[i]}`); - break; - } - } - } - } else { - bladeburner.postToConsole("Invalid usage of 'skill' console command: skill [action] [name]"); - bladeburner.postToConsole("Use 'help skill' for more info"); - } - break; - case 3: - const skillName = args[2]; - const skill = Skills[skillName]; - if (skill == null || !(skill instanceof Skill)) { - bladeburner.postToConsole("Invalid skill name (Note that it is case-sensitive): " + skillName); - } - if (args[1].toLowerCase() === "list") { - let level = 0; - if (bladeburner.skills[skill.name] !== undefined) { - level = bladeburner.skills[skill.name]; - } - bladeburner.postToConsole(skill.name + ": Level " + formatNumber(level)); - } else if (args[1].toLowerCase() === "level") { - let currentLevel = 0; - if (bladeburner.skills[skillName] && !isNaN(bladeburner.skills[skillName])) { - currentLevel = bladeburner.skills[skillName]; - } - const pointCost = skill.calculateCost(currentLevel); - if (bladeburner.skillPoints >= pointCost) { - bladeburner.skillPoints -= pointCost; - bladeburner.upgradeSkill(skill); - bladeburner.log(skill.name + " upgraded to Level " + bladeburner.skills[skillName]); - } else { - bladeburner.postToConsole("You do not have enough Skill Points to upgrade bladeburner. You need " + formatNumber(pointCost, 0)); - } - - } else { - bladeburner.postToConsole("Invalid usage of 'skill' console command: skill [action] [name]"); - bladeburner.postToConsole("Use 'help skill' for more info"); - } - break; - default: - bladeburner.postToConsole("Invalid usage of 'skill' console command: skill [action] [name]"); - bladeburner.postToConsole("Use 'help skill' for more info"); - break; - } -} - - -export function executeLogConsoleCommand(bladeburner: IBladeburner, args: string[]): void { - if (args.length < 3) { - bladeburner.postToConsole("Invalid usage of log command: log [enable/disable] [action/event]"); - bladeburner.postToConsole("Use 'help log' for more details and examples"); - return; - } - - let flag = true; - if (args[1].toLowerCase().includes("d")) {flag = false;} // d for disable - - switch (args[2].toLowerCase()) { - case "general": - case "gen": - bladeburner.logging.general = flag; - bladeburner.log("Logging " + (flag ? "enabled" : "disabled") + " for general actions"); - break; - case "contract": - case "contracts": - bladeburner.logging.contracts = flag; - bladeburner.log("Logging " + (flag ? "enabled" : "disabled") + " for Contracts"); - break; - case "ops": - case "op": - case "operations": - case "operation": - bladeburner.logging.ops = flag; - bladeburner.log("Logging " + (flag ? "enabled" : "disabled") + " for Operations"); - break; - case "blackops": - case "blackop": - case "black operations": - case "black operation": - bladeburner.logging.blackops = flag; - bladeburner.log("Logging " + (flag ? "enabled" : "disabled") + " for BlackOps"); - break; - case "event": - case "events": - bladeburner.logging.events = flag; - bladeburner.log("Logging " + (flag ? "enabled" : "disabled") + " for events"); - break; - case "all": - bladeburner.logging.general = flag; - bladeburner.logging.contracts = flag; - bladeburner.logging.ops = flag; - bladeburner.logging.blackops = flag; - bladeburner.logging.events = flag; - bladeburner.log("Logging " + (flag ? "enabled" : "disabled") + " for everything"); - break; - default: - bladeburner.postToConsole("Invalid action/event type specified: " + args[2]); - bladeburner.postToConsole("Examples of valid action/event identifiers are: [general, contracts, ops, blackops, events]"); - break; - } -} - -export function executeHelpConsoleCommand(bladeburner: IBladeburner, args: string[]): void { - if (args.length === 1) { - for(const line of ConsoleHelpText.helpList){ - bladeburner.postToConsole(line); - } - } else { - for (let i = 1; i < args.length; ++i) { - if(!(args[i] in ConsoleHelpText)) continue; - const helpText = ConsoleHelpText[args[i]]; - for(const line of helpText){ - bladeburner.postToConsole(line); - } - } - } -} - -export function executeAutomateConsoleCommand(bladeburner: IBladeburner, args: string[]): void { - if (args.length !== 2 && args.length !== 4) { - bladeburner.postToConsole("Invalid use of 'automate' command: automate [var] [val] [hi/low]. Use 'help automate' for more info"); - return; - } - - // Enable/Disable - if (args.length === 2) { - const flag = args[1]; - if (flag.toLowerCase() === "status") { - bladeburner.postToConsole("Automation: " + (bladeburner.automateEnabled ? "enabled" : "disabled")); - if (bladeburner.automateEnabled) { - bladeburner.postToConsole("When your stamina drops to " + formatNumber(bladeburner.automateThreshLow, 0) + - ", you will automatically switch to " + bladeburner.automateActionLow.name + - ". When your stamina recovers to " + - formatNumber(bladeburner.automateThreshHigh, 0) + ", you will automatically " + - "switch to " + bladeburner.automateActionHigh.name + "."); - } - - } else if (flag.toLowerCase().includes("en")) { - if (!(bladeburner.automateActionLow instanceof ActionIdentifier) || - !(bladeburner.automateActionHigh instanceof ActionIdentifier)) { - return bladeburner.log("Failed to enable automation. Actions were not set"); - } - bladeburner.automateEnabled = true; - bladeburner.log("Bladeburner automation enabled"); - } else if (flag.toLowerCase().includes("d")) { - bladeburner.automateEnabled = false; - bladeburner.log("Bladeburner automation disabled"); - } else { - bladeburner.log("Invalid argument for 'automate' console command: " + args[1]); - } - return; - } - - // Set variables - if (args.length === 4) { - const variable = args[1]; - const val = args[2]; - - let highLow = false; // True for high, false for low - if (args[3].toLowerCase().includes("hi")) {highLow = true;} - - switch (variable) { - case "general": - case "gen": - if (GeneralActions[val] != null) { - const action = new ActionIdentifier({ - type:ActionTypes[val], name:val, - }); - if (highLow) { - bladeburner.automateActionHigh = action; - } else { - bladeburner.automateActionLow = action; - } - bladeburner.log("Automate (" + (highLow ? "HIGH" : "LOW") + ") action set to " + val); - } else { - bladeburner.postToConsole("Invalid action name specified: " + val); - } - break; - case "contract": - case "contracts": - if (bladeburner.contracts[val] != null) { - const action = new ActionIdentifier({ - type:ActionTypes.Contract, name:val, - }); - if (highLow) { - bladeburner.automateActionHigh = action; - } else { - bladeburner.automateActionLow = action; - } - bladeburner.log("Automate (" + (highLow ? "HIGH" : "LOW") + ") action set to " + val); - } else { - bladeburner.postToConsole("Invalid contract name specified: " + val); - } - break; - case "ops": - case "op": - case "operations": - case "operation": - if (bladeburner.operations[val] != null) { - const action = new ActionIdentifier({ - type:ActionTypes.Operation, name:val, - }); - if (highLow) { - bladeburner.automateActionHigh = action; - } else { - bladeburner.automateActionLow = action; - } - bladeburner.log("Automate (" + (highLow ? "HIGH" : "LOW") + ") action set to " + val); - } else { - bladeburner.postToConsole("Invalid Operation name specified: " + val); - } - break; - case "stamina": - if (isNaN(parseFloat(val))) { - bladeburner.postToConsole("Invalid value specified for stamina threshold (must be numeric): " + val); - } else { - if (highLow) { - bladeburner.automateThreshHigh = Number(val); - } else { - bladeburner.automateThreshLow = Number(val); - } - bladeburner.log("Automate (" + (highLow ? "HIGH" : "LOW") + ") stamina threshold set to " + val); - } - break; - default: - break; - } - - return; - } -} - -export function parseCommandArguments(command: string): string[] { - /** - * Returns an array with command and its arguments in each index. - * e.g. skill "blade's intuition" foo returns [skill, blade's intuition, foo] - * The input to the fn will be trimmed and will have all whitespace replaced w/ a single space - */ - const args = []; - let start = 0; - let i = 0; - while (i < command.length) { - const c = command.charAt(i); - if (c === '"') { // Double quotes - const endQuote = command.indexOf('"', i+1); - if (endQuote !== -1 && (endQuote === command.length-1 || command.charAt(endQuote+1) === " ")) { - args.push(command.substr(i+1, (endQuote - i - 1))); - if (endQuote === command.length-1) { - start = i = endQuote+1; - } else { - start = i = endQuote+2; // Skip the space - } - continue; - } - } else if (c === "'") { // Single quotes, same thing as above - const endQuote = command.indexOf("'", i+1); - if (endQuote !== -1 && (endQuote === command.length-1 || command.charAt(endQuote+1) === " ")) { - args.push(command.substr(i+1, (endQuote - i - 1))); - if (endQuote === command.length-1) { - start = i = endQuote+1; - } else { - start = i = endQuote+2; // Skip the space - } - continue; - } - } else if (c === " ") { - args.push(command.substr(start, i-start)); - start = i+1; - } - ++i; - } - if (start !== i) {args.push(command.substr(start, i-start));} - return args; -} - -export function executeConsoleCommand(bladeburner: IBladeburner, player: IPlayer, command: string) { - command = command.trim(); - command = command.replace(/\s\s+/g, ' '); // Replace all whitespace w/ a single space - - const args = parseCommandArguments(command); - if (args.length <= 0) return; // Log an error? - - switch(args[0].toLowerCase()) { - case "automate": - executeAutomateConsoleCommand(bladeburner, args); - break; - case "clear": - case "cls": - clearConsole(bladeburner); - break; - case "help": - executeHelpConsoleCommand(bladeburner, args); - break; - case "log": - executeLogConsoleCommand(bladeburner, args); - break; - case "skill": - executeSkillConsoleCommand(bladeburner, args); - break; - case "start": - executeStartConsoleCommand(bladeburner, player, args); - break; - case "stop": - resetAction(bladeburner); - break; - default: - bladeburner.postToConsole("Invalid console command"); - break; - } -} - -// Handles a potential series of commands (comm1; comm2; comm3;) -export function executeConsoleCommands(bladeburner: IBladeburner, player: IPlayer, commands: string): void { - try { - // Console History - if (bladeburner.consoleHistory[bladeburner.consoleHistory.length-1] != commands) { - bladeburner.consoleHistory.push(commands); - if (bladeburner.consoleHistory.length > 50) { - bladeburner.consoleHistory.splice(0, 1); - } - } - - const arrayOfCommands = commands.split(";"); - for (let i = 0; i < arrayOfCommands.length; ++i) { - executeConsoleCommand(bladeburner, player, arrayOfCommands[i]); - } - } catch(e) { - exceptionAlert(e); - } -} - -export function clearConsole(bladeburner: IBladeburner): void { - bladeburner.consoleLogs.length = 0; -} - -export function triggerMigration(bladeburner: IBladeburner, sourceCityName: string): void { - let destCityName = BladeburnerConstants.CityNames[getRandomInt(0, 5)]; - while (destCityName === sourceCityName) { - destCityName = BladeburnerConstants.CityNames[getRandomInt(0, 5)]; - } - const destCity = bladeburner.cities[destCityName]; - const sourceCity = bladeburner.cities[sourceCityName]; - if (destCity == null || sourceCity == null) { - throw new Error("Failed to find City with name: " + destCityName); - } - const rand = Math.random(); - let percentage = getRandomInt(3, 15) / 100; - - if (rand < 0.05 && sourceCity.comms > 0) { // 5% chance for community migration - percentage *= getRandomInt(2, 4); // Migration increases population change - --sourceCity.comms; - ++destCity.comms; - } - const count = Math.round(sourceCity.pop * percentage); - sourceCity.pop -= count; - destCity.pop += count; -} - -export function triggerPotentialMigration(bladeburner: IBladeburner, sourceCityName: string, chance: number): void { - if (chance == null || isNaN(chance)) { - console.error("Invalid 'chance' parameter passed into Bladeburner.triggerPotentialMigration()"); - } - if (chance > 1) {chance /= 100;} - if (Math.random() < chance) {triggerMigration(bladeburner, sourceCityName);} -} - -export function randomEvent(bladeburner: IBladeburner): void { - const chance = Math.random(); - - // Choose random source/destination city for events - const sourceCityName = BladeburnerConstants.CityNames[getRandomInt(0, 5)]; - const sourceCity = bladeburner.cities[sourceCityName]; - if (!(sourceCity instanceof City)) { - throw new Error("sourceCity was not a City object in Bladeburner.randomEvent()"); - } - - let destCityName = BladeburnerConstants.CityNames[getRandomInt(0, 5)]; - while (destCityName === sourceCityName) { - destCityName = BladeburnerConstants.CityNames[getRandomInt(0, 5)]; - } - const destCity = bladeburner.cities[destCityName]; - - if (!(sourceCity instanceof City) || !(destCity instanceof City)) { - throw new Error("sourceCity/destCity was not a City object in Bladeburner.randomEvent()"); - } - - if (chance <= 0.05) { - // New Synthoid Community, 5% - ++sourceCity.comms; - const percentage = getRandomInt(10, 20) / 100; - const count = Math.round(sourceCity.pop * percentage); - sourceCity.pop += count; - if (bladeburner.logging.events) { - bladeburner.log("Intelligence indicates that a new Synthoid community was formed in a city"); - } - } else if (chance <= 0.1) { - // Synthoid Community Migration, 5% - if (sourceCity.comms <= 0) { - // If no comms in source city, then instead trigger a new Synthoid community event - ++sourceCity.comms; - const percentage = getRandomInt(10, 20) / 100; - const count = Math.round(sourceCity.pop * percentage); - sourceCity.pop += count; - if (bladeburner.logging.events) { - bladeburner.log("Intelligence indicates that a new Synthoid community was formed in a city"); - } - } else { - --sourceCity.comms; - ++destCity.comms; - - // Change pop - const percentage = getRandomInt(10, 20) / 100; - const count = Math.round(sourceCity.pop * percentage); - sourceCity.pop -= count; - destCity.pop += count; - - if (bladeburner.logging.events) { - bladeburner.log("Intelligence indicates that a Synthoid community migrated from " + sourceCityName + " to some other city"); - } - } - } else if (chance <= 0.3) { - // New Synthoids (non community), 20% - const percentage = getRandomInt(8, 24) / 100; - const count = Math.round(sourceCity.pop * percentage); - sourceCity.pop += count; - if (bladeburner.logging.events) { - bladeburner.log("Intelligence indicates that the Synthoid population of " + sourceCityName + " just changed significantly"); - } - } else if (chance <= 0.5) { - // Synthoid migration (non community) 20% - triggerMigration(bladeburner, sourceCityName); - if (bladeburner.logging.events) { - bladeburner.log("Intelligence indicates that a large number of Synthoids migrated from " + sourceCityName + " to some other city"); - } - } else if (chance <= 0.7) { - // Synthoid Riots (+chaos), 20% - sourceCity.chaos += 1; - sourceCity.chaos *= (1 + getRandomInt(5, 20) / 100); - if (bladeburner.logging.events) { - bladeburner.log("Tensions between Synthoids and humans lead to riots in " + sourceCityName + "! Chaos increased"); - } - } else if (chance <= 0.9) { - // Less Synthoids, 20% - const percentage = getRandomInt(8, 20) / 100; - const count = Math.round(sourceCity.pop * percentage); - sourceCity.pop -= count; - if (bladeburner.logging.events) { - bladeburner.log("Intelligence indicates that the Synthoid population of " + sourceCityName + " just changed significantly"); - } - } - // 10% chance of nothing happening -} - - -/** - * Process stat gains from Contracts, Operations, and Black Operations - * @param action(Action obj) - Derived action class - * @param success(bool) - Whether action was successful - */ -export function gainActionStats(bladeburner: IBladeburner, player: IPlayer, action: IAction, success: boolean): void { - const difficulty = action.getDifficulty(); - - /** - * Gain multiplier based on difficulty. If it changes then the - * same variable calculated in completeAction() needs to change too - */ - const difficultyMult = Math.pow(difficulty, BladeburnerConstants.DiffMultExponentialFactor) + difficulty / BladeburnerConstants.DiffMultLinearFactor; - - const time = bladeburner.actionTimeToComplete; - const successMult = success ? 1 : 0.5; - - const unweightedGain = time * BladeburnerConstants.BaseStatGain * successMult * difficultyMult; - const unweightedIntGain = time * BladeburnerConstants.BaseIntGain * successMult * difficultyMult; - const skillMult = bladeburner.skillMultipliers.expGain; - player.gainHackingExp(unweightedGain * action.weights.hack * player.hacking_exp_mult * skillMult); - player.gainStrengthExp(unweightedGain * action.weights.str * player.strength_exp_mult * skillMult); - player.gainDefenseExp(unweightedGain * action.weights.def * player.defense_exp_mult * skillMult); - player.gainDexterityExp(unweightedGain * action.weights.dex * player.dexterity_exp_mult * skillMult); - player.gainAgilityExp(unweightedGain * action.weights.agi * player.agility_exp_mult * skillMult); - player.gainCharismaExp(unweightedGain * action.weights.cha * player.charisma_exp_mult * skillMult); - let intExp = unweightedIntGain * action.weights.int * skillMult; - if (intExp > 1) { - intExp = Math.pow(intExp, 0.8); - } - player.gainIntelligenceExp(intExp); -} - -export function getDiplomacyEffectiveness(bladeburner: IBladeburner, player: IPlayer): number { - // Returns a decimal by which the city's chaos level should be multiplied (e.g. 0.98) - const CharismaLinearFactor = 1e3; - const CharismaExponentialFactor = 0.045; - - const charismaEff = Math.pow(player.charisma, CharismaExponentialFactor) + player.charisma / CharismaLinearFactor; - return (100 - charismaEff) / 100; -} - -export function getRecruitmentSuccessChance(bladeburner: IBladeburner, player: IPlayer): number { - return Math.pow(player.charisma, 0.45) / (bladeburner.teamSize + 1); -} - -export function getRecruitmentTime(bladeburner: IBladeburner, player: IPlayer): number { - const effCharisma = player.charisma * bladeburner.skillMultipliers.effCha; - const charismaFactor = Math.pow(effCharisma, 0.81) + effCharisma / 90; - return Math.max(10, Math.round(BladeburnerConstants.BaseRecruitmentTimeNeeded - charismaFactor)); -} - -export function resetSkillMultipliers(bladeburner: IBladeburner): void { - bladeburner.skillMultipliers = { - successChanceAll: 1, - successChanceStealth: 1, - successChanceKill: 1, - successChanceContract: 1, - successChanceOperation: 1, - successChanceEstimate: 1, - actionTime: 1, - effHack: 1, - effStr: 1, - effDef: 1, - effDex: 1, - effAgi: 1, - effCha: 1, - effInt: 1, - stamina: 1, - money: 1, - expGain: 1, - }; -} - -export function updateSkillMultipliers(bladeburner: IBladeburner): void { - resetSkillMultipliers(bladeburner); - for (const skillName in bladeburner.skills) { - if (bladeburner.skills.hasOwnProperty(skillName)) { - const skill = Skills[skillName]; - if (skill == null) { - throw new Error("Could not find Skill Object for: " + skillName); - } - const level = bladeburner.skills[skillName]; - if (level == null || level <= 0) {continue;} //Not upgraded - - const multiplierNames = Object.keys(bladeburner.skillMultipliers); - for (let i = 0; i < multiplierNames.length; ++i) { - const multiplierName = multiplierNames[i]; - if (skill.getMultiplier(multiplierName) != null && !isNaN(skill.getMultiplier(multiplierName))) { - const value = skill.getMultiplier(multiplierName) * level; - let multiplierValue = 1 + (value / 100); - if (multiplierName === "actionTime") { - multiplierValue = 1 - (value / 100); - } - bladeburner.skillMultipliers[multiplierName] *= multiplierValue; - } - } - } - } -} - - -// Sets the player to the "IDLE" action -export function resetAction(bladeburner: IBladeburner): void { - bladeburner.action = new ActionIdentifier({type:ActionTypes.Idle}); -} - -export function completeOperation(bladeburner: IBladeburner, success: boolean): void { - if (bladeburner.action.type !== ActionTypes.Operation) { - throw new Error("completeOperation() called even though current action is not an Operation"); - } - const action = getActionObject(bladeburner, bladeburner.action); - if (action == null) { - throw new Error("Failed to get Contract/Operation Object for: " + bladeburner.action.name); - } - - // Calculate team losses - const teamCount = action.teamCount; - if (teamCount >= 1) { - let max; - if (success) { - max = Math.ceil(teamCount/2); - } else { - max = Math.floor(teamCount) - } - const losses = getRandomInt(0, max); - bladeburner.teamSize -= losses; - bladeburner.teamLost += losses; - if (bladeburner.logging.ops && losses > 0) { - bladeburner.log("Lost " + formatNumber(losses, 0) + " team members during this " + action.name); - } - } - - const city = bladeburner.getCurrentCity(); - switch (action.name) { - case "Investigation": - if (success) { - city.improvePopulationEstimateByPercentage(0.4 * bladeburner.skillMultipliers.successChanceEstimate); - if (Math.random() < (0.02 * bladeburner.skillMultipliers.successChanceEstimate)) { - city.improveCommunityEstimate(1); - } - } else { - triggerPotentialMigration(bladeburner, bladeburner.city, 0.1); - } - break; - case "Undercover Operation": - if (success) { - city.improvePopulationEstimateByPercentage(0.8 * bladeburner.skillMultipliers.successChanceEstimate); - if (Math.random() < (0.02 * bladeburner.skillMultipliers.successChanceEstimate)) { - city.improveCommunityEstimate(1); - } - } else { - triggerPotentialMigration(bladeburner, bladeburner.city, 0.15); - } - break; - case "Sting Operation": - if (success) { - city.changePopulationByPercentage(-0.1, {changeEstEqually:true, nonZero:true}); - } - city.changeChaosByCount(0.1); - break; - case "Raid": - if (success) { - city.changePopulationByPercentage(-1, {changeEstEqually:true, nonZero:true}); - --city.comms; - --city.commsEst; - } else { - const change = getRandomInt(-10, -5) / 10; - city.changePopulationByPercentage(change, {nonZero:true, changeEstEqually:false}); - } - city.changeChaosByPercentage(getRandomInt(1, 5)); - break; - case "Stealth Retirement Operation": - if (success) { - city.changePopulationByPercentage(-0.5, {changeEstEqually:true,nonZero:true}); - } - city.changeChaosByPercentage(getRandomInt(-3, -1)); - break; - case "Assassination": - if (success) { - city.changePopulationByCount(-1, {estChange:-1, estOffset: 0}); - } - city.changeChaosByPercentage(getRandomInt(-5, 5)); - break; - default: - throw new Error("Invalid Action name in completeOperation: " + bladeburner.action.name); - } -} - -export function getActionObject(bladeburner: IBladeburner, actionId: IActionIdentifier): IAction | null { - /** - * Given an ActionIdentifier object, returns the corresponding - * GeneralAction, Contract, Operation, or BlackOperation object - */ - switch (actionId.type) { - case ActionTypes["Contract"]: - return bladeburner.contracts[actionId.name]; - case ActionTypes["Operation"]: - return bladeburner.operations[actionId.name]; - case ActionTypes["BlackOp"]: - case ActionTypes["BlackOperation"]: - return BlackOperations[actionId.name]; - case ActionTypes["Training"]: - return GeneralActions["Training"]; - case ActionTypes["Field Analysis"]: - return GeneralActions["Field Analysis"]; - case ActionTypes["Recruitment"]: - return GeneralActions["Recruitment"]; - case ActionTypes["Diplomacy"]: - return GeneralActions["Diplomacy"]; - case ActionTypes["Hyperbolic Regeneration Chamber"]: - return GeneralActions["Hyperbolic Regeneration Chamber"]; - default: - return null; - } -} - -export function completeContract(bladeburner: IBladeburner, success: boolean): void { - if (bladeburner.action.type !== ActionTypes.Contract) { - throw new Error("completeContract() called even though current action is not a Contract"); - } - var city = bladeburner.getCurrentCity(); - if (success) { - switch (bladeburner.action.name) { - case "Tracking": - // Increase estimate accuracy by a relatively small amount - city.improvePopulationEstimateByCount(getRandomInt(100, 1e3)); - break; - case "Bounty Hunter": - city.changePopulationByCount(-1, {estChange:-1, estOffset: 0}); - city.changeChaosByCount(0.02); - break; - case "Retirement": - city.changePopulationByCount(-1, {estChange:-1, estOffset: 0}); - city.changeChaosByCount(0.04); - break; - default: - throw new Error("Invalid Action name in completeContract: " + bladeburner.action.name); - } - } -} - -export function completeAction(bladeburner: IBladeburner, player: IPlayer): void { - switch (bladeburner.action.type) { - case ActionTypes["Contract"]: - case ActionTypes["Operation"]: { - try { - const isOperation = (bladeburner.action.type === ActionTypes["Operation"]); - const action = getActionObject(bladeburner, bladeburner.action); - if (action == null) { - throw new Error("Failed to get Contract/Operation Object for: " + bladeburner.action.name); - } - const difficulty = action.getDifficulty(); - const difficultyMultiplier = Math.pow(difficulty, BladeburnerConstants.DiffMultExponentialFactor) + difficulty / BladeburnerConstants.DiffMultLinearFactor; - const rewardMultiplier = Math.pow(action.rewardFac, action.level-1); - - // Stamina loss is based on difficulty - bladeburner.stamina -= (BladeburnerConstants.BaseStaminaLoss * difficultyMultiplier); - if (bladeburner.stamina < 0) {bladeburner.stamina = 0;} - - // Process Contract/Operation success/failure - if (action.attempt(bladeburner)) { - gainActionStats(bladeburner, player, action, true); - ++action.successes; - --action.count; - - // Earn money for contracts - let moneyGain = 0; - if (!isOperation) { - moneyGain = BladeburnerConstants.ContractBaseMoneyGain * rewardMultiplier * bladeburner.skillMultipliers.money; - player.gainMoney(moneyGain); - player.recordMoneySource(moneyGain, "bladeburner"); - } - - if (isOperation) { - action.setMaxLevel(BladeburnerConstants.OperationSuccessesPerLevel); - } else { - action.setMaxLevel(BladeburnerConstants.ContractSuccessesPerLevel); - } - if (action.rankGain) { - const gain = addOffset(action.rankGain * rewardMultiplier * BitNodeMultipliers.BladeburnerRank, 10); - changeRank(bladeburner, player, gain); - if (isOperation && bladeburner.logging.ops) { - bladeburner.log(action.name + " successfully completed! Gained " + formatNumber(gain, 3) + " rank"); - } else if (!isOperation && bladeburner.logging.contracts) { - bladeburner.log(action.name + " contract successfully completed! Gained " + formatNumber(gain, 3) + " rank and " + numeralWrapper.formatMoney(moneyGain)); - } - } - isOperation ? completeOperation(bladeburner, true) : completeContract(bladeburner, true); - } else { - gainActionStats(bladeburner, player, action, false); - ++action.failures; - let loss = 0, damage = 0; - if (action.rankLoss) { - loss = addOffset(action.rankLoss * rewardMultiplier, 10); - changeRank(bladeburner, player, -1 * loss); - } - if (action.hpLoss) { - damage = action.hpLoss * difficultyMultiplier; - damage = Math.ceil(addOffset(damage, 10)); - bladeburner.hpLost += damage; - const cost = calculateHospitalizationCost(player, damage); - if (player.takeDamage(damage)) { - ++bladeburner.numHosp; - bladeburner.moneyLost += cost; - } - } - let logLossText = ""; - if (loss > 0) {logLossText += "Lost " + formatNumber(loss, 3) + " rank. ";} - if (damage > 0) {logLossText += "Took " + formatNumber(damage, 0) + " damage.";} - if (isOperation && bladeburner.logging.ops) { - bladeburner.log(action.name + " failed! " + logLossText); - } else if (!isOperation && bladeburner.logging.contracts) { - bladeburner.log(action.name + " contract failed! " + logLossText); - } - isOperation ? completeOperation(bladeburner, false) : completeContract(bladeburner, false); - } - if (action.autoLevel) {action.level = action.maxLevel;} // Autolevel - startAction(bladeburner,player, bladeburner.action); // Repeat action - } catch(e) { - exceptionAlert(e); - } - break; - } - case ActionTypes["BlackOp"]: - case ActionTypes["BlackOperation"]: { - try { - const action = getActionObject(bladeburner, bladeburner.action); - if (action == null || !(action instanceof BlackOperation)) { - throw new Error("Failed to get BlackOperation Object for: " + bladeburner.action.name); - } - const difficulty = action.getDifficulty(); - const difficultyMultiplier = Math.pow(difficulty, BladeburnerConstants.DiffMultExponentialFactor) + difficulty / BladeburnerConstants.DiffMultLinearFactor; - - // Stamina loss is based on difficulty - bladeburner.stamina -= (BladeburnerConstants.BaseStaminaLoss * difficultyMultiplier); - if (bladeburner.stamina < 0) {bladeburner.stamina = 0;} - - // Team loss variables - const teamCount = action.teamCount; - let teamLossMax; - - if (action.attempt(bladeburner)) { - gainActionStats(bladeburner, player, action, true); - action.count = 0; - bladeburner.blackops[action.name] = true; - let rankGain = 0; - if (action.rankGain) { - rankGain = addOffset(action.rankGain * BitNodeMultipliers.BladeburnerRank, 10); - changeRank(bladeburner, player, rankGain); - } - teamLossMax = Math.ceil(teamCount/2); - - // Operation Daedalus - if (action.name === "Operation Daedalus") { - resetAction(bladeburner); - return hackWorldDaemon(player.bitNodeN); - } - - if (bladeburner.logging.blackops) { - bladeburner.log(action.name + " successful! Gained " + formatNumber(rankGain, 1) + " rank"); - } - } else { - gainActionStats(bladeburner, player, action, false); - let rankLoss = 0; - let damage = 0; - if (action.rankLoss) { - rankLoss = addOffset(action.rankLoss, 10); - changeRank(bladeburner, player, -1 * rankLoss); - } - if (action.hpLoss) { - damage = action.hpLoss * difficultyMultiplier; - damage = Math.ceil(addOffset(damage, 10)); - const cost = calculateHospitalizationCost(player, damage); - if (player.takeDamage(damage)) { - ++bladeburner.numHosp; - bladeburner.moneyLost += cost; - } - } - teamLossMax = Math.floor(teamCount); - - if (bladeburner.logging.blackops) { - bladeburner.log(action.name + " failed! Lost " + formatNumber(rankLoss, 1) + " rank and took " + formatNumber(damage, 0) + " damage"); - } - } - - resetAction(bladeburner); // Stop regardless of success or fail - - // Calculate team lossses - if (teamCount >= 1) { - const losses = getRandomInt(1, teamLossMax); - bladeburner.teamSize -= losses; - bladeburner.teamLost += losses; - if (bladeburner.logging.blackops) { - bladeburner.log("You lost " + formatNumber(losses, 0) + " team members during " + action.name); - } - } - } catch(e) { - exceptionAlert(e); - } - break; - } - case ActionTypes["Training"]: { - bladeburner.stamina -= (0.5 * BladeburnerConstants.BaseStaminaLoss); - const strExpGain = 30 * player.strength_exp_mult, - defExpGain = 30 * player.defense_exp_mult, - dexExpGain = 30 * player.dexterity_exp_mult, - agiExpGain = 30 * player.agility_exp_mult, - staminaGain = 0.04 * bladeburner.skillMultipliers.stamina; - player.gainStrengthExp(strExpGain); - player.gainDefenseExp(defExpGain); - player.gainDexterityExp(dexExpGain); - player.gainAgilityExp(agiExpGain); - bladeburner.staminaBonus += (staminaGain); - if (bladeburner.logging.general) { - bladeburner.log("Training completed. Gained: " + - formatNumber(strExpGain, 1) + " str exp, " + - formatNumber(defExpGain, 1) + " def exp, " + - formatNumber(dexExpGain, 1) + " dex exp, " + - formatNumber(agiExpGain, 1) + " agi exp, " + - formatNumber(staminaGain, 3) + " max stamina"); - } - startAction(bladeburner,player, bladeburner.action); // Repeat action - break; - } - case ActionTypes["FieldAnalysis"]: - case ActionTypes["Field Analysis"]: { - // Does not use stamina. Effectiveness depends on hacking, int, and cha - let eff = 0.04 * Math.pow(player.hacking_skill, 0.3) + - 0.04 * Math.pow(player.intelligence, 0.9) + - 0.02 * Math.pow(player.charisma, 0.3); - eff *= player.bladeburner_analysis_mult; - if (isNaN(eff) || eff < 0) { - throw new Error("Field Analysis Effectiveness calculated to be NaN or negative"); - } - const hackingExpGain = 20 * player.hacking_exp_mult, - charismaExpGain = 20 * player.charisma_exp_mult; - player.gainHackingExp(hackingExpGain); - player.gainIntelligenceExp(BladeburnerConstants.BaseIntGain); - player.gainCharismaExp(charismaExpGain); - changeRank(bladeburner, player, 0.1 * BitNodeMultipliers.BladeburnerRank); - bladeburner.getCurrentCity().improvePopulationEstimateByPercentage(eff * bladeburner.skillMultipliers.successChanceEstimate); - if (bladeburner.logging.general) { - bladeburner.log("Field analysis completed. Gained 0.1 rank, " + formatNumber(hackingExpGain, 1) + " hacking exp, and " + formatNumber(charismaExpGain, 1) + " charisma exp"); - } - startAction(bladeburner,player, bladeburner.action); // Repeat action - break; - } - case ActionTypes["Recruitment"]: { - const successChance = getRecruitmentSuccessChance(bladeburner, player); - if (Math.random() < successChance) { - const expGain = 2 * BladeburnerConstants.BaseStatGain * bladeburner.actionTimeToComplete; - player.gainCharismaExp(expGain); - ++bladeburner.teamSize; - if (bladeburner.logging.general) { - bladeburner.log("Successfully recruited a team member! Gained " + formatNumber(expGain, 1) + " charisma exp"); - } - } else { - const expGain = BladeburnerConstants.BaseStatGain * bladeburner.actionTimeToComplete; - player.gainCharismaExp(expGain); - if (bladeburner.logging.general) { - bladeburner.log("Failed to recruit a team member. Gained " + formatNumber(expGain, 1) + " charisma exp"); - } - } - startAction(bladeburner,player, bladeburner.action); // Repeat action - break; - } - case ActionTypes["Diplomacy"]: { - let eff = getDiplomacyEffectiveness(bladeburner, player); - bladeburner.getCurrentCity().chaos *= eff; - if (bladeburner.getCurrentCity().chaos < 0) { bladeburner.getCurrentCity().chaos = 0; } - if (bladeburner.logging.general) { - bladeburner.log(`Diplomacy completed. Chaos levels in the current city fell by ${numeralWrapper.formatPercentage(1 - eff)}`); - } - startAction(bladeburner,player, bladeburner.action); // Repeat Action - break; - } - case ActionTypes["Hyperbolic Regeneration Chamber"]: { - player.regenerateHp(BladeburnerConstants.HrcHpGain); - - const staminaGain = bladeburner.maxStamina * (BladeburnerConstants.HrcStaminaGain / 100); - bladeburner.stamina = Math.min(bladeburner.maxStamina, bladeburner.stamina + staminaGain); - startAction(bladeburner,player, bladeburner.action); - if (bladeburner.logging.general) { - bladeburner.log(`Rested in Hyperbolic Regeneration Chamber. Restored ${BladeburnerConstants.HrcHpGain} HP and gained ${numeralWrapper.formatStamina(staminaGain)} stamina`); - } - break; - } - default: - console.error(`Bladeburner.completeAction() called for invalid action: ${bladeburner.action.type}`); - break; - } -} - -export function changeRank(bladeburner: IBladeburner, player: IPlayer, change: number): void { - if (isNaN(change)) {throw new Error("NaN passed into Bladeburner.changeRank()");} - bladeburner.rank += change; - if (bladeburner.rank < 0) {bladeburner.rank = 0;} - bladeburner.maxRank = Math.max(bladeburner.rank, bladeburner.maxRank); - - var bladeburnersFactionName = "Bladeburners"; - if (factionExists(bladeburnersFactionName)) { - var bladeburnerFac = Factions[bladeburnersFactionName]; - if (!(bladeburnerFac instanceof Faction)) { - throw new Error("Could not properly get Bladeburner Faction object in Bladeburner UI Overview Faction button"); - } - if (bladeburnerFac.isMember) { - var favorBonus = 1 + (bladeburnerFac.favor / 100); - bladeburnerFac.playerReputation += (BladeburnerConstants.RankToFactionRepFactor * change * player.faction_rep_mult * favorBonus); - } - } - - // Gain skill points - var rankNeededForSp = (bladeburner.totalSkillPoints+1) * BladeburnerConstants.RanksPerSkillPoint; - if (bladeburner.maxRank >= rankNeededForSp) { - // Calculate how many skill points to gain - var gainedSkillPoints = Math.floor((bladeburner.maxRank - rankNeededForSp) / BladeburnerConstants.RanksPerSkillPoint + 1); - bladeburner.skillPoints += gainedSkillPoints; - bladeburner.totalSkillPoints += gainedSkillPoints; - } -} - -export function processAction(bladeburner: IBladeburner, player: IPlayer, seconds: number): void { - if (bladeburner.action.type === ActionTypes["Idle"]) return; - if (bladeburner.actionTimeToComplete <= 0) { - throw new Error(`Invalid actionTimeToComplete value: ${bladeburner.actionTimeToComplete}, type; ${bladeburner.action.type}`); - } - if (!(bladeburner.action instanceof ActionIdentifier)) { - throw new Error("Bladeburner.action is not an ActionIdentifier Object"); - } - - // If the previous action went past its completion time, add to the next action - // This is not added inmediatly in case the automation changes the action - bladeburner.actionTimeCurrent += seconds + bladeburner.actionTimeOverflow; - bladeburner.actionTimeOverflow = 0; - if (bladeburner.actionTimeCurrent >= bladeburner.actionTimeToComplete) { - bladeburner.actionTimeOverflow = bladeburner.actionTimeCurrent - bladeburner.actionTimeToComplete; - return completeAction(bladeburner, player); - } -} - -export function startAction(bladeburner: IBladeburner, player: IPlayer, actionId: IActionIdentifier): void { - if (actionId == null) return; - bladeburner.action = actionId; - bladeburner.actionTimeCurrent = 0; - switch (actionId.type) { - case ActionTypes["Idle"]: - bladeburner.actionTimeToComplete = 0; - break; - case ActionTypes["Contract"]: - try { - const action = getActionObject(bladeburner, actionId); - if (action == null) { - throw new Error("Failed to get Contract Object for: " + actionId.name); - } - if (action.count < 1) {return resetAction(bladeburner);} - bladeburner.actionTimeToComplete = action.getActionTime(bladeburner); - } catch(e) { - exceptionAlert(e); - } - break; - case ActionTypes["Operation"]: { - try { - const action = getActionObject(bladeburner, actionId); - if (action == null) { - throw new Error ("Failed to get Operation Object for: " + actionId.name); - } - if (action.count < 1) {return resetAction(bladeburner);} - if (actionId.name === "Raid" && bladeburner.getCurrentCity().commsEst === 0) {return resetAction(bladeburner);} - bladeburner.actionTimeToComplete = action.getActionTime(bladeburner); - } catch(e) { - exceptionAlert(e); - } - break; - } - case ActionTypes["BlackOp"]: - case ActionTypes["BlackOperation"]: { - try { - // Safety measure - don't repeat BlackOps that are already done - if (bladeburner.blackops[actionId.name] != null) { - resetAction(bladeburner); - bladeburner.log("Error: Tried to start a Black Operation that had already been completed"); - break; - } - - const action = getActionObject(bladeburner, actionId); - if (action == null) { - throw new Error("Failed to get BlackOperation object for: " + actionId.name); - } - bladeburner.actionTimeToComplete = action.getActionTime(bladeburner); - } catch(e) { - exceptionAlert(e); - } - break; - } - case ActionTypes["Recruitment"]: - bladeburner.actionTimeToComplete = getRecruitmentTime(bladeburner, player); - break; - case ActionTypes["Training"]: - case ActionTypes["FieldAnalysis"]: - case ActionTypes["Field Analysis"]: - bladeburner.actionTimeToComplete = 30; - break; - case ActionTypes["Diplomacy"]: - case ActionTypes["Hyperbolic Regeneration Chamber"]: - bladeburner.actionTimeToComplete = 60; - break; - default: - throw new Error("Invalid Action Type in startAction(Bladeburner,player, ): " + actionId.type); - break; - } -} - -export function calculateStaminaPenalty(bladeburner: IBladeburner): number { - return Math.min(1, bladeburner.stamina / (0.5 * bladeburner.maxStamina)); -} - -export function calculateStaminaGainPerSecond(bladeburner: IBladeburner, player: IPlayer): number { - const effAgility = player.agility * bladeburner.skillMultipliers.effAgi; - const maxStaminaBonus = bladeburner.maxStamina / BladeburnerConstants.MaxStaminaToGainFactor; - const gain = (BladeburnerConstants.StaminaGainPerSecond + maxStaminaBonus) * Math.pow(effAgility, 0.17); - return gain * (bladeburner.skillMultipliers.stamina * player.bladeburner_stamina_gain_mult); -} - -export function calculateMaxStamina(bladeburner: IBladeburner, player: IPlayer) { - const effAgility = player.agility * bladeburner.skillMultipliers.effAgi; - let maxStamina = (Math.pow(effAgility, 0.8) + bladeburner.staminaBonus) * - bladeburner.skillMultipliers.stamina * - player.bladeburner_max_stamina_mult; - if (bladeburner.maxStamina !== maxStamina) { - const oldMax = bladeburner.maxStamina; - bladeburner.maxStamina = maxStamina; - bladeburner.stamina = bladeburner.maxStamina * bladeburner.stamina / oldMax; - } - if (isNaN(maxStamina)) {throw new Error("Max Stamina calculated to be NaN in Bladeburner.calculateMaxStamina()");} -} - -export function create(bladeburner: IBladeburner): void { - bladeburner.contracts["Tracking"] = new Contract({ - name:"Tracking", - desc:"Identify and locate Synthoids. This contract involves reconnaissance " + - "and information-gathering ONLY. Do NOT engage. Stealth is of the utmost importance.

        " + - "Successfully completing Tracking contracts will slightly improve your Synthoid population estimate for " + - "whatever city you are currently in.", - baseDifficulty:125,difficultyFac:1.02,rewardFac:1.041, - rankGain:0.3, hpLoss:0.5, - count:getRandomInt(25, 150), countGrowth:getRandomInt(5, 75)/10, - weights:{hack:0,str:0.05,def:0.05,dex:0.35,agi:0.35,cha:0.1, int:0.05}, - decays:{hack:0,str:0.91,def:0.91,dex:0.91,agi:0.91,cha:0.9, int:1}, - isStealth:true, - }); - bladeburner.contracts["Bounty Hunter"] = new Contract({ - name:"Bounty Hunter", - desc:"Hunt down and capture fugitive Synthoids. These Synthoids are wanted alive.

        " + - "Successfully completing a Bounty Hunter contract will lower the population in your " + - "current city, and will also increase its chaos level.", - baseDifficulty:250, difficultyFac:1.04,rewardFac:1.085, - rankGain:0.9, hpLoss:1, - count:getRandomInt(5, 150), countGrowth:getRandomInt(5, 75)/10, - weights:{hack:0,str:0.15,def:0.15,dex:0.25,agi:0.25,cha:0.1, int:0.1}, - decays:{hack:0,str:0.91,def:0.91,dex:0.91,agi:0.91,cha:0.8, int:0.9}, - isKill:true, - }); - bladeburner.contracts["Retirement"] = new Contract({ - name:"Retirement", - desc:"Hunt down and retire (kill) rogue Synthoids.

        " + - "Successfully completing a Retirement contract will lower the population in your current " + - "city, and will also increase its chaos level.", - baseDifficulty:200, difficultyFac:1.03, rewardFac:1.065, - rankGain:0.6, hpLoss:1, - count:getRandomInt(5, 150), countGrowth:getRandomInt(5, 75)/10, - weights:{hack:0,str:0.2,def:0.2,dex:0.2,agi:0.2,cha:0.1, int:0.1}, - decays:{hack:0,str:0.91,def:0.91,dex:0.91,agi:0.91,cha:0.8, int:0.9}, - isKill:true, - }); - - bladeburner.operations["Investigation"] = new Operation({ - name:"Investigation", - desc:"As a field agent, investigate and identify Synthoid " + - "populations, movements, and operations.

        Successful " + - "Investigation ops will increase the accuracy of your " + - "synthoid data.

        " + - "You will NOT lose HP from failed Investigation ops.", - baseDifficulty:400, difficultyFac:1.03,rewardFac:1.07,reqdRank:25, - rankGain:2.2, rankLoss:0.2, - count:getRandomInt(1, 100), countGrowth:getRandomInt(10, 40)/10, - weights:{hack:0.25,str:0.05,def:0.05,dex:0.2,agi:0.1,cha:0.25, int:0.1}, - decays:{hack:0.85,str:0.9,def:0.9,dex:0.9,agi:0.9,cha:0.7, int:0.9}, - isStealth:true, - }); - bladeburner.operations["Undercover Operation"] = new Operation({ - name:"Undercover Operation", - desc:"Conduct undercover operations to identify hidden " + - "and underground Synthoid communities and organizations.

        " + - "Successful Undercover ops will increase the accuracy of your synthoid " + - "data.", - baseDifficulty:500, difficultyFac:1.04, rewardFac:1.09, reqdRank:100, - rankGain:4.4, rankLoss:0.4, hpLoss:2, - count:getRandomInt(1, 100), countGrowth:getRandomInt(10, 40)/10, - weights:{hack:0.2,str:0.05,def:0.05,dex:0.2,agi:0.2,cha:0.2, int:0.1}, - decays:{hack:0.8,str:0.9,def:0.9,dex:0.9,agi:0.9,cha:0.7, int:0.9}, - isStealth:true, - }); - bladeburner.operations["Sting Operation"] = new Operation({ - name:"Sting Operation", - desc:"Conduct a sting operation to bait and capture particularly " + - "notorious Synthoid criminals.", - baseDifficulty:650, difficultyFac:1.04, rewardFac:1.095, reqdRank:500, - rankGain:5.5, rankLoss:0.5, hpLoss:2.5, - count:getRandomInt(1, 150), countGrowth:getRandomInt(3, 40)/10, - weights:{hack:0.25,str:0.05,def:0.05,dex:0.25,agi:0.1,cha:0.2, int:0.1}, - decays:{hack:0.8,str:0.85,def:0.85,dex:0.85,agi:0.85,cha:0.7, int:0.9}, - isStealth:true, - }); - bladeburner.operations["Raid"] = new Operation({ - name:"Raid", - desc:"Lead an assault on a known Synthoid community. Note that " + - "there must be an existing Synthoid community in your current city " + - "in order for this Operation to be successful.", - baseDifficulty:800, difficultyFac:1.045, rewardFac:1.1, reqdRank:3000, - rankGain:55,rankLoss:2.5,hpLoss:50, - count:getRandomInt(1, 150), countGrowth:getRandomInt(2, 40)/10, - weights:{hack:0.1,str:0.2,def:0.2,dex:0.2,agi:0.2,cha:0, int:0.1}, - decays:{hack:0.7,str:0.8,def:0.8,dex:0.8,agi:0.8,cha:0, int:0.9}, - isKill:true, - }); - bladeburner.operations["Stealth Retirement Operation"] = new Operation({ - name:"Stealth Retirement Operation", - desc:"Lead a covert operation to retire Synthoids. The " + - "objective is to complete the task without " + - "drawing any attention. Stealth and discretion are key.", - baseDifficulty:1000, difficultyFac:1.05, rewardFac:1.11, reqdRank:20e3, - rankGain:22, rankLoss:2, hpLoss:10, - count:getRandomInt(1, 150), countGrowth:getRandomInt(1, 20)/10, - weights:{hack:0.1,str:0.1,def:0.1,dex:0.3,agi:0.3,cha:0, int:0.1}, - decays:{hack:0.7,str:0.8,def:0.8,dex:0.8,agi:0.8,cha:0, int:0.9}, - isStealth:true, isKill:true, - }); - bladeburner.operations["Assassination"] = new Operation({ - name:"Assassination", - desc:"Assassinate Synthoids that have been identified as " + - "important, high-profile social and political leaders " + - "in the Synthoid communities.", - baseDifficulty:1500, difficultyFac:1.06, rewardFac:1.14, reqdRank:50e3, - rankGain:44, rankLoss:4, hpLoss:5, - count:getRandomInt(1, 150), countGrowth:getRandomInt(1, 20)/10, - weights:{hack:0.1,str:0.1,def:0.1,dex:0.3,agi:0.3,cha:0, int:0.1}, - decays:{hack:0.6,str:0.8,def:0.8,dex:0.8,agi:0.8,cha:0, int:0.8}, - isStealth:true, isKill:true, - }); -} - -export function process(bladeburner: IBladeburner, player: IPlayer): void { - // Edge case condition...if Operation Daedalus is complete trigger the BitNode - if (redPillFlag === false && bladeburner.blackops.hasOwnProperty("Operation Daedalus")) { - return hackWorldDaemon(player.bitNodeN); - } - - // If the Player starts doing some other actions, set action to idle and alert - if (Augmentations[AugmentationNames.BladesSimulacrum].owned === false && player.isWorking) { - if (bladeburner.action.type !== ActionTypes["Idle"]) { - let msg = "Your Bladeburner action was cancelled because you started doing something else."; - if (bladeburner.automateEnabled) { - msg += `

        Your automation was disabled as well. You will have to re-enable it through the Bladeburner console` - bladeburner.automateEnabled = false; - } - if (!Settings.SuppressBladeburnerPopup) { - dialogBoxCreate(msg); - } - } - resetAction(bladeburner); - } - - // If the Player has no Stamina, set action to idle - if (bladeburner.stamina <= 0) { - bladeburner.log("Your Bladeburner action was cancelled because your stamina hit 0"); - resetAction(bladeburner); - } - - // A 'tick' for this mechanic is one second (= 5 game cycles) - if (bladeburner.storedCycles >= BladeburnerConstants.CyclesPerSecond) { - let seconds = Math.floor(bladeburner.storedCycles / BladeburnerConstants.CyclesPerSecond); - seconds = Math.min(seconds, 5); // Max of 5 'ticks' - bladeburner.storedCycles -= seconds * BladeburnerConstants.CyclesPerSecond; - - // Stamina - calculateMaxStamina(bladeburner, player); - bladeburner.stamina += (calculateStaminaGainPerSecond(bladeburner, player) * seconds); - bladeburner.stamina = Math.min(bladeburner.maxStamina, bladeburner.stamina); - - // Count increase for contracts/operations - for (const contract of (Object.values(bladeburner.contracts) as Contract[])) { - contract.count += (seconds * contract.countGrowth/BladeburnerConstants.ActionCountGrowthPeriod); - } - for (const op of (Object.values(bladeburner.operations) as Operation[])) { - op.count += (seconds * op.countGrowth/BladeburnerConstants.ActionCountGrowthPeriod); - } - - // Chaos goes down very slowly - for (const cityName of BladeburnerConstants.CityNames) { - const city = bladeburner.cities[cityName]; - if (!(city instanceof City)) {throw new Error("Invalid City object when processing passive chaos reduction in Bladeburner.process");} - city.chaos -= (0.0001 * seconds); - city.chaos = Math.max(0, city.chaos); - } - - // Random Events - bladeburner.randomEventCounter -= seconds; - if (bladeburner.randomEventCounter <= 0) { - randomEvent(bladeburner); - // Add instead of setting because we might have gone over the required time for the event - bladeburner.randomEventCounter += getRandomInt(240, 600); - } - - processAction(bladeburner, player, seconds); - - // Automation - if (bladeburner.automateEnabled) { - // Note: Do NOT set bladeburner.action = bladeburner.automateActionHigh/Low since it creates a reference - if (bladeburner.stamina <= bladeburner.automateThreshLow) { - if (bladeburner.action.name !== bladeburner.automateActionLow.name || bladeburner.action.type !== bladeburner.automateActionLow.type) { - bladeburner.action = new ActionIdentifier({type: bladeburner.automateActionLow.type, name: bladeburner.automateActionLow.name}); - startAction(bladeburner, player, bladeburner.action); - } - } else if (bladeburner.stamina >= bladeburner.automateThreshHigh) { - if (bladeburner.action.name !== bladeburner.automateActionHigh.name || bladeburner.action.type !== bladeburner.automateActionHigh.type) { - bladeburner.action = new ActionIdentifier({type: bladeburner.automateActionHigh.type, name: bladeburner.automateActionHigh.name}); - startAction(bladeburner, player, bladeburner.action); - } - } - } - } -} - - - - - - - - -export function prestige(bladeburner: IBladeburner): void { - resetAction(bladeburner); - const bladeburnerFac = Factions["Bladeburners"]; - if (bladeburner.rank >= BladeburnerConstants.RankNeededForFaction) { - joinFaction(bladeburnerFac); - } -} - -export function storeCycles(bladeburner: IBladeburner, numCycles: number = 1): void { - bladeburner.storedCycles += numCycles; -} - -export function getCurrentCity(bladeburner: IBladeburner): City { - const city = bladeburner.cities[bladeburner.city]; - if (!(city instanceof City)) { - throw new Error("Bladeburner.getCurrentCity() did not properly return a City object"); - } - return city; -} - -export function upgradeSkill(bladeburner: IBladeburner, skill: Skill) { - // This does NOT handle deduction of skill points - const skillName = skill.name; - if (bladeburner.skills[skillName]) { - ++bladeburner.skills[skillName]; - } else { - bladeburner.skills[skillName] = 1; - } - if (isNaN(bladeburner.skills[skillName]) || bladeburner.skills[skillName] < 0) { - throw new Error("Level of Skill " + skillName + " is invalid: " + bladeburner.skills[skillName]); - } - updateSkillMultipliers(bladeburner); -} - -// Bladeburner Console Window -export function postToConsole(bladeburner: IBladeburner, input: string, saveToLogs: boolean = true) { - const MaxConsoleEntries = 100; - if (saveToLogs) { - bladeburner.consoleLogs.push(input); - if (bladeburner.consoleLogs.length > MaxConsoleEntries) { - bladeburner.consoleLogs.shift(); - } - } -} - -export function log(bladeburner: IBladeburner, input: string) { - // Adds a timestamp and then just calls postToConsole - bladeburner.postToConsole(`[${getTimestamp()}] ${input}`); -} - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////// Netscript Fns ///////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -export function getTypeAndNameFromActionId(bladeburner: IBladeburner, actionId: IActionIdentifier): {type: string, name: string} { - const res = {type: '', name: ''}; - const types = Object.keys(ActionTypes); - for (let i = 0; i < types.length; ++i) { - if (actionId.type === ActionTypes[types[i]]) { - res.type = types[i]; - break; - } - } - if (res.type == null) {res.type = "Idle";} - - res.name = actionId.name != null ? actionId.name : "Idle"; - return res; -} -export function getContractNamesNetscriptFn(bladeburner: IBladeburner): string[] { - return Object.keys(bladeburner.contracts); -} -export function getOperationNamesNetscriptFn(bladeburner: IBladeburner): string[] { - return Object.keys(bladeburner.operations); -} -export function getBlackOpNamesNetscriptFn(bladeburner: IBladeburner): string[] { - return Object.keys(BlackOperations); -} -export function getGeneralActionNamesNetscriptFn(bladeburner: IBladeburner): string[] { - return Object.keys(GeneralActions); -} -export function getSkillNamesNetscriptFn(bladeburner: IBladeburner): string[] { - return Object.keys(Skills); -} -export function startActionNetscriptFn(bladeburner: IBladeburner, player: IPlayer, type: string, name: string, workerScript: WorkerScript) { - const errorLogText = `Invalid action: type='${type}' name='${name}'`; - const actionId = getActionIdFromTypeAndName(bladeburner, type, name); - if (actionId == null) { - workerScript.log("bladeburner.startAction", errorLogText); - return false; - } - - // Special logic for Black Ops - if (actionId.type === ActionTypes["BlackOp"]) { - // Can't start a BlackOp if you don't have the required rank - const action = getActionObject(bladeburner, actionId); - if(action == null) throw new Error('Action not found ${actionId.type}, ${actionId.name}'); - if(!(action instanceof BlackOperation)) throw new Error(`Action should be BlackOperation but isn't`); - const blackOp = (action as BlackOperation); - if (action.reqdRank > bladeburner.rank) { - workerScript.log("bladeburner.startAction", `Insufficient rank to start Black Op '${actionId.name}'.`); - return false; - } - - // Can't start a BlackOp if its already been done - if (bladeburner.blackops[actionId.name] != null) { - workerScript.log("bladeburner.startAction", `Black Op ${actionId.name} has already been completed.`); - return false; - } - - // Can't start a BlackOp if you haven't done the one before it - var blackops = []; - for (const nm in BlackOperations) { - if (BlackOperations.hasOwnProperty(nm)) { - blackops.push(nm); - } - } - blackops.sort(function(a, b) { - return (BlackOperations[a].reqdRank - BlackOperations[b].reqdRank); // Sort black ops in intended order - }); - - let i = blackops.indexOf(actionId.name); - if (i === -1) { - workerScript.log("bladeburner.startAction", `Invalid Black Op: '${name}'`); - return false; - } - - if (i > 0 && bladeburner.blackops[blackops[i-1]] == null) { - workerScript.log("bladeburner.startAction", `Preceding Black Op must be completed before starting '${actionId.name}'.`); - return false; - } - } - - try { - startAction(bladeburner, player, actionId); - workerScript.log("bladeburner.startAction", `Starting bladeburner action with type '${type}' and name ${name}"`); - return true; - } catch(e) { - resetAction(bladeburner); - workerScript.log("bladeburner.startAction", errorLogText); - return false; - } -} -export function getActionTimeNetscriptFn(bladeburner: IBladeburner, player: IPlayer, type: string, name: string, workerScript: WorkerScript) { - const errorLogText = `Invalid action: type='${type}' name='${name}'` - const actionId = getActionIdFromTypeAndName(bladeburner, type, name); - if (actionId == null) { - workerScript.log("bladeburner.getActionTime", errorLogText); - return -1; - } - - const actionObj = getActionObject(bladeburner, actionId); - if (actionObj == null) { - workerScript.log("bladeburner.getActionTime", errorLogText); - return -1; - } - - switch (actionId.type) { - case ActionTypes["Contract"]: - case ActionTypes["Operation"]: - case ActionTypes["BlackOp"]: - case ActionTypes["BlackOperation"]: - return actionObj.getActionTime(bladeburner); - case ActionTypes["Training"]: - case ActionTypes["Field Analysis"]: - case ActionTypes["FieldAnalysis"]: - return 30; - case ActionTypes["Recruitment"]: - return getRecruitmentTime(bladeburner, player); - case ActionTypes["Diplomacy"]: - case ActionTypes["Hyperbolic Regeneration Chamber"]: - return 60; - default: - workerScript.log("bladeburner.getActionTime", errorLogText); - return -1; - } -} -export function getActionEstimatedSuccessChanceNetscriptFn(bladeburner: IBladeburner, player: IPlayer, type: string, name: string, workerScript: WorkerScript) { - const errorLogText = `Invalid action: type='${type}' name='${name}'` - const actionId = getActionIdFromTypeAndName(bladeburner, type, name); - if (actionId == null) { - workerScript.log("bladeburner.getActionEstimatedSuccessChance", errorLogText); - return -1; - } - - const actionObj = getActionObject(bladeburner, actionId); - if (actionObj == null) { - workerScript.log("bladeburner.getActionEstimatedSuccessChance", errorLogText); - return -1; - } - - switch (actionId.type) { - case ActionTypes["Contract"]: - case ActionTypes["Operation"]: - case ActionTypes["BlackOp"]: - case ActionTypes["BlackOperation"]: - return actionObj.getSuccessChance(bladeburner, {est:true}); - case ActionTypes["Training"]: - case ActionTypes["Field Analysis"]: - case ActionTypes["FieldAnalysis"]: - return 1; - case ActionTypes["Recruitment"]: - return getRecruitmentSuccessChance(bladeburner, player); - default: - workerScript.log("bladeburner.getActionEstimatedSuccessChance", errorLogText); - return -1; - } -} -export function getActionCountRemainingNetscriptFn(bladeburner: IBladeburner, type: string, name: string, workerScript: WorkerScript) { - const errorLogText = `Invalid action: type='${type}' name='${name}'`; - const actionId = getActionIdFromTypeAndName(bladeburner, type, name); - if (actionId == null) { - workerScript.log("bladeburner.getActionCountRemaining", errorLogText); - return -1; - } - - const actionObj = getActionObject(bladeburner, actionId); - if (actionObj == null) { - workerScript.log("bladeburner.getActionCountRemaining", errorLogText); - return -1; - } - - switch (actionId.type) { - case ActionTypes["Contract"]: - case ActionTypes["Operation"]: - return Math.floor( actionObj.count ); - case ActionTypes["BlackOp"]: - case ActionTypes["BlackOperation"]: - if (bladeburner.blackops[name] != null) { - return 0; - } else { - return 1; - } - case ActionTypes["Training"]: - case ActionTypes["Field Analysis"]: - case ActionTypes["FieldAnalysis"]: - return Infinity; - default: - workerScript.log("bladeburner.getActionCountRemaining", errorLogText); - return -1; - } -} -export function getSkillLevelNetscriptFn(bladeburner: IBladeburner, skillName: string, workerScript: WorkerScript) { - if (skillName === "" || !Skills.hasOwnProperty(skillName)) { - workerScript.log("bladeburner.getSkillLevel", `Invalid skill: '${skillName}'`); - return -1; - } - - if (bladeburner.skills[skillName] == null) { - return 0; - } else { - return bladeburner.skills[skillName]; - } -} -export function getSkillUpgradeCostNetscriptFn(bladeburner: IBladeburner, skillName: string, workerScript: WorkerScript) { - if (skillName === "" || !Skills.hasOwnProperty(skillName)) { - workerScript.log("bladeburner.getSkillUpgradeCost", `Invalid skill: '${skillName}'`); - return -1; - } - - const skill = Skills[skillName]; - if (bladeburner.skills[skillName] == null) { - return skill.calculateCost(0); - } else { - return skill.calculateCost(bladeburner.skills[skillName]); - } -} -export function upgradeSkillNetscriptFn(bladeburner: IBladeburner, skillName: string, workerScript: WorkerScript) { - const errorLogText = `Invalid skill: '${skillName}'`; - if (!Skills.hasOwnProperty(skillName)) { - workerScript.log("bladeburner.upgradeSkill", errorLogText); - return false; - } - - const skill = Skills[skillName]; - let currentLevel = 0; - if (bladeburner.skills[skillName] && !isNaN(bladeburner.skills[skillName])) { - currentLevel = bladeburner.skills[skillName]; - } - const cost = skill.calculateCost(currentLevel); - - if(skill.maxLvl && currentLevel >= skill.maxLvl) { - workerScript.log("bladeburner.upgradeSkill", `Skill '${skillName}' is already maxed.`); - return false; - } - - if (bladeburner.skillPoints < cost) { - workerScript.log("bladeburner.upgradeSkill", `You do not have enough skill points to upgrade ${skillName} (You have ${bladeburner.skillPoints}, you need ${cost})`); - return false; - } - - bladeburner.skillPoints -= cost; - bladeburner.upgradeSkill(skill); - workerScript.log("bladeburner.upgradeSkill", `'${skillName}' upgraded to level ${bladeburner.skills[skillName]}`); - return true; -} -export function getTeamSizeNetscriptFn(bladeburner: IBladeburner, type: string, name: string, workerScript: WorkerScript): number { - if (type === "" && name === "") { - return bladeburner.teamSize; - } - - const errorLogText = `Invalid action: type='${type}' name='${name}'`; - const actionId = getActionIdFromTypeAndName(bladeburner, type, name); - if (actionId == null) { - workerScript.log("bladeburner.getTeamSize", errorLogText); - return -1; - } - - const actionObj = getActionObject(bladeburner, actionId); - if (actionObj == null) { - workerScript.log("bladeburner.getTeamSize", errorLogText); - return -1; - } - - if (actionId.type === ActionTypes["Operation"] || - actionId.type === ActionTypes["BlackOp"] || - actionId.type === ActionTypes["BlackOperation"]) { - return actionObj.teamCount; - } else { - return 0; - } -} -export function setTeamSizeNetscriptFn(bladeburner: IBladeburner, type: string, name: string, size: number, workerScript: WorkerScript): number { - const errorLogText = `Invalid action: type='${type}' name='${name}'`; - const actionId = getActionIdFromTypeAndName(bladeburner, type, name); - if (actionId == null) { - workerScript.log("bladeburner.setTeamSize", errorLogText); - return -1; - } - - if (actionId.type !== ActionTypes["Operation"] && - actionId.type !== ActionTypes["BlackOp"] && - actionId.type !== ActionTypes["BlackOperation"]) { - workerScript.log("bladeburner.setTeamSize", "Only valid for 'Operations' and 'BlackOps'"); - return -1; - } - - const actionObj = getActionObject(bladeburner, actionId); - if (actionObj == null) { - workerScript.log("bladeburner.setTeamSize", errorLogText); - return -1; - } - - let sanitizedSize = Math.round(size); - if (isNaN(sanitizedSize) || sanitizedSize < 0) { - workerScript.log("bladeburner.setTeamSize", `Invalid size: ${size}`); - return -1; - } - if (bladeburner.teamSize < sanitizedSize) {sanitizedSize = bladeburner.teamSize;} - actionObj.teamCount = sanitizedSize; - workerScript.log("bladeburner.setTeamSize", `Team size for '${name}' set to ${sanitizedSize}.`); - return sanitizedSize; -} -export function joinBladeburnerFactionNetscriptFn(bladeburner: IBladeburner, workerScript: WorkerScript): boolean { - var bladeburnerFac = Factions["Bladeburners"]; - if (bladeburnerFac.isMember) { - return true; - } else if (bladeburner.rank >= BladeburnerConstants.RankNeededForFaction) { - joinFaction(bladeburnerFac); - workerScript.log("bladeburner.joinBladeburnerFaction", "Joined Bladeburners faction."); - return true; - } else { - workerScript.log("bladeburner.joinBladeburnerFaction", `You do not have the required rank (${bladeburner.rank}/${BladeburnerConstants.RankNeededForFaction}).`); - return false; - } -} +Reviver.constructors.Bladeburner = Bladeburner; \ No newline at end of file diff --git a/src/Bladeburner/IBladeburner.ts b/src/Bladeburner/IBladeburner.ts index a9cb20e01..b69c2c40a 100644 --- a/src/Bladeburner/IBladeburner.ts +++ b/src/Bladeburner/IBladeburner.ts @@ -1,6 +1,7 @@ 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"; @@ -74,4 +75,31 @@ export interface IBladeburner { 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; +} \ No newline at end of file diff --git a/src/Bladeburner/ui/BlackOpElem.tsx b/src/Bladeburner/ui/BlackOpElem.tsx index c5b9c8f5a..f1961c35a 100644 --- a/src/Bladeburner/ui/BlackOpElem.tsx +++ b/src/Bladeburner/ui/BlackOpElem.tsx @@ -10,7 +10,6 @@ import { createPopup } from "../../ui/React/createPopup"; import { TeamSizePopup } from "./TeamSizePopup"; import { IBladeburner } from "../IBladeburner"; import { IPlayer } from "../../PersonObjects/IPlayer"; -import { startAction } from "../Bladeburner"; interface IProps { bladeburner: IBladeburner; @@ -35,7 +34,7 @@ export function BlackOpElem(props: IProps): React.ReactElement { function onStart() { props.bladeburner.action.type = ActionTypes.BlackOperation; props.bladeburner.action.name = props.action.name; - startAction(props.bladeburner, props.player, props.bladeburner.action); + props.bladeburner.startAction(props.player, props.bladeburner.action); setRerender(old => !old); } diff --git a/src/Bladeburner/ui/ContractElem.tsx b/src/Bladeburner/ui/ContractElem.tsx index 789317e11..d479b4299 100644 --- a/src/Bladeburner/ui/ContractElem.tsx +++ b/src/Bladeburner/ui/ContractElem.tsx @@ -9,7 +9,6 @@ import { stealthIcon, killIcon } from "../data/Icons"; import { BladeburnerConstants } from "../data/Constants"; import { IBladeburner } from "../IBladeburner"; import { IPlayer } from "../../PersonObjects/IPlayer"; -import { startAction } from "../Bladeburner"; interface IProps { bladeburner: IBladeburner; @@ -29,19 +28,19 @@ export function ContractElem(props: IProps): React.ReactElement { function onStart() { props.bladeburner.action.type = ActionTypes.Contract; props.bladeburner.action.name = props.action.name; - startAction(props.bladeburner, props.player, props.bladeburner.action); + props.bladeburner.startAction(props.player, props.bladeburner.action); setRerender(old => !old); } function increaseLevel() { ++props.action.level; - if (isActive) startAction(props.bladeburner, props.player, props.bladeburner.action); + if (isActive) props.bladeburner.startAction(props.player, props.bladeburner.action); setRerender(old => !old); } function decreaseLevel() { --props.action.level; - if (isActive) startAction(props.bladeburner, props.player, props.bladeburner.action); + if (isActive) props.bladeburner.startAction(props.player, props.bladeburner.action); setRerender(old => !old); } diff --git a/src/Bladeburner/ui/GeneralActionElem.tsx b/src/Bladeburner/ui/GeneralActionElem.tsx index bd312035f..d6a23e8bb 100644 --- a/src/Bladeburner/ui/GeneralActionElem.tsx +++ b/src/Bladeburner/ui/GeneralActionElem.tsx @@ -10,8 +10,6 @@ import { BladeburnerConstants } from "../data/Constants"; import { IBladeburner } from "../IBladeburner"; import { IPlayer } from "../../PersonObjects/IPlayer"; -import { startAction } from "../Bladeburner"; - interface IProps { bladeburner: IBladeburner; player: IPlayer; @@ -26,7 +24,7 @@ export function GeneralActionElem(props: IProps): React.ReactElement { function onStart() { props.bladeburner.action.type = ActionTypes[(props.action.name as string)]; props.bladeburner.action.name = props.action.name; - startAction(props.bladeburner, props.player, props.bladeburner.action); + props.bladeburner.startAction(props.player, props.bladeburner.action); setRerender(old => !old); } diff --git a/src/Bladeburner/ui/OperationElem.tsx b/src/Bladeburner/ui/OperationElem.tsx index f3d07eb62..482d08703 100644 --- a/src/Bladeburner/ui/OperationElem.tsx +++ b/src/Bladeburner/ui/OperationElem.tsx @@ -11,7 +11,6 @@ import { createPopup } from "../../ui/React/createPopup"; import { TeamSizePopup } from "./TeamSizePopup"; import { IBladeburner } from "../IBladeburner"; import { IPlayer } from "../../PersonObjects/IPlayer"; -import { startAction } from "../Bladeburner"; interface IProps { bladeburner: IBladeburner; @@ -31,7 +30,7 @@ export function OperationElem(props: IProps): React.ReactElement { function onStart() { props.bladeburner.action.type = ActionTypes.Operation; props.bladeburner.action.name = props.action.name; - startAction(props.bladeburner, props.player, props.bladeburner.action); + props.bladeburner.startAction(props.player, props.bladeburner.action); setRerender(old => !old); } @@ -46,13 +45,13 @@ export function OperationElem(props: IProps): React.ReactElement { function increaseLevel() { ++props.action.level; - if (isActive) startAction(props.bladeburner, props.player, props.bladeburner.action); + if (isActive) props.bladeburner.startAction(props.player, props.bladeburner.action); setRerender(old => !old); } function decreaseLevel() { --props.action.level; - if (isActive) startAction(props.bladeburner, props.player, props.bladeburner.action); + if (isActive) props.bladeburner.startAction(props.player, props.bladeburner.action); setRerender(old => !old); } diff --git a/src/engine.jsx b/src/engine.jsx index 1c5379aea..4e9cdc854 100644 --- a/src/engine.jsx +++ b/src/engine.jsx @@ -884,7 +884,7 @@ const Engine = { } if (Player.bladeburner instanceof Bladeburner) { try { - ProcessBladeburner(Player.bladeburner, Player); + Player.bladeburner.process(Player); } catch(e) { exceptionAlert("Exception caught in Bladeburner.process(): " + e); } From 9af9bf58b6a34b433b8705b8b4d798e264bf6aed Mon Sep 17 00:00:00 2001 From: Olivier Gagnon Date: Mon, 16 Aug 2021 20:01:19 -0400 Subject: [PATCH 11/15] fix final bugs --- src/Bladeburner/Bladeburner.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Bladeburner/Bladeburner.ts b/src/Bladeburner/Bladeburner.ts index 00bb64764..aee7a28da 100644 --- a/src/Bladeburner/Bladeburner.ts +++ b/src/Bladeburner/Bladeburner.ts @@ -219,7 +219,7 @@ export class Bladeburner implements IBladeburner { } } - postToConsole(input: string, saveToLogs?: boolean): void { + postToConsole(input: string, saveToLogs: boolean = true): void { const MaxConsoleEntries = 100; if (saveToLogs) { this.consoleLogs.push(input); @@ -969,7 +969,6 @@ export class Bladeburner implements IBladeburner { effDex: 1, effAgi: 1, effCha: 1, - effInt: 1, stamina: 1, money: 1, expGain: 1, From fa78b3f421101c6537f94e4a91855724c90054da Mon Sep 17 00:00:00 2001 From: Olivier Gagnon Date: Tue, 17 Aug 2021 23:52:18 -0400 Subject: [PATCH 12/15] Fix React list without keys, fix int miscalculation of blade skills. --- src/Bladeburner/Bladeburner.ts | 1 + src/Bladeburner/Skill.ts | 2 +- src/Bladeburner/ui/TravelPopup.tsx | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Bladeburner/Bladeburner.ts b/src/Bladeburner/Bladeburner.ts index aee7a28da..f736388d6 100644 --- a/src/Bladeburner/Bladeburner.ts +++ b/src/Bladeburner/Bladeburner.ts @@ -969,6 +969,7 @@ export class Bladeburner implements IBladeburner { effDex: 1, effAgi: 1, effCha: 1, + effInt: 1, stamina: 1, money: 1, expGain: 1, diff --git a/src/Bladeburner/Skill.ts b/src/Bladeburner/Skill.ts index fb590efd3..7021d4311 100644 --- a/src/Bladeburner/Skill.ts +++ b/src/Bladeburner/Skill.ts @@ -124,7 +124,7 @@ export class Skill { if(name === "stamina") return this.stamina; if(name === "money") return this.money; if(name === "expGain") return this.expGain; - return 1; + return 0; } } diff --git a/src/Bladeburner/ui/TravelPopup.tsx b/src/Bladeburner/ui/TravelPopup.tsx index 205f3da54..ef1ec83dc 100644 --- a/src/Bladeburner/ui/TravelPopup.tsx +++ b/src/Bladeburner/ui/TravelPopup.tsx @@ -24,7 +24,7 @@ export function TravelPopup(props: IProps): React.ReactElement { {BladeburnerConstants.CityNames.map(city => // Reusing this css class...it adds a border and makes it // so that background color changes when you hover -
        travel(city)}> {city}
        )} From 5c9236031038212d268da2807c2de624b7da9f83 Mon Sep 17 00:00:00 2001 From: Olivier Gagnon Date: Wed, 18 Aug 2021 00:08:23 -0400 Subject: [PATCH 13/15] convert a few variables to const. --- src/engine.jsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/engine.jsx b/src/engine.jsx index 4e9cdc854..a58a6e2ce 100644 --- a/src/engine.jsx +++ b/src/engine.jsx @@ -608,9 +608,9 @@ const Engine = { // Main Game Loop idleTimer: function() { // Get time difference - var _thisUpdate = new Date().getTime(); - var diff = _thisUpdate - Engine._lastUpdate; - var offset = diff % Engine._idleSpeed; + const _thisUpdate = new Date().getTime(); + let diff = _thisUpdate - Engine._lastUpdate; + const offset = diff % Engine._idleSpeed; // Divide this by cycle time to determine how many cycles have elapsed since last update diff = Math.floor(diff / Engine._idleSpeed); @@ -626,7 +626,7 @@ const Engine = { }, updateGame: function(numCycles = 1) { - var time = numCycles * Engine._idleSpeed; + const time = numCycles * Engine._idleSpeed; if (Player.totalPlaytime == null) {Player.totalPlaytime = 0;} if (Player.playtimeSinceLastAug == null) {Player.playtimeSinceLastAug = 0;} if (Player.playtimeSinceLastBitnode == null) {Player.playtimeSinceLastBitnode = 0;} From d6b349b6ff8bdb65eb2716923bb76cc00a9b5949 Mon Sep 17 00:00:00 2001 From: Olivier Gagnon Date: Wed, 18 Aug 2021 00:51:51 -0400 Subject: [PATCH 14/15] dialogBoxCreate now uses the same logic as other popups, now all popup can be dismissed with escape. --- src/Message/MessageHelpers.js | 8 ++-- src/ui/React/Popup.tsx | 14 ++++++- src/ui/React/createPopup.tsx | 31 +++++++++----- utils/DialogBox.d.ts | 2 - utils/DialogBox.jsx | 76 ----------------------------------- utils/DialogBox.tsx | 38 ++++++++++++++++++ 6 files changed, 74 insertions(+), 95 deletions(-) delete mode 100644 utils/DialogBox.d.ts delete mode 100644 utils/DialogBox.jsx create mode 100644 utils/DialogBox.tsx diff --git a/src/Message/MessageHelpers.js b/src/Message/MessageHelpers.js index 4932c7be2..79bda4adc 100644 --- a/src/Message/MessageHelpers.js +++ b/src/Message/MessageHelpers.js @@ -7,7 +7,7 @@ import { Player } from "../Player"; import { redPillFlag } from "../RedPill"; import { GetServerByHostname } from "../Server/ServerHelpers"; import { Settings } from "../Settings/Settings"; -import { dialogBoxCreate, dialogBoxOpened} from "../../utils/DialogBox"; +import { dialogBoxCreate} from "../../utils/DialogBox"; import { Reviver } from "../../utils/JSONReviver"; //Sends message to player, including a pop up @@ -59,12 +59,10 @@ function checkForMessagesToSend() { } if (redpill && redpillOwned && Player.sourceFiles.length === 0 && !redPillFlag && !inMission) { - if (!dialogBoxOpened) { - sendMessage(redpill, true); - } + sendMessage(redpill, true); } else if (redpill && redpillOwned) { //If player has already destroyed a BitNode, message is not forced - if (!redPillFlag && !inMission && !dialogBoxOpened) { + if (!redPillFlag && !inMission) { sendMessage(redpill); } } else if (jumper0 && !jumper0.recvd && Player.hacking_skill >= 25) { diff --git a/src/ui/React/Popup.tsx b/src/ui/React/Popup.tsx index cc777eaa9..c7d7e3004 100644 --- a/src/ui/React/Popup.tsx +++ b/src/ui/React/Popup.tsx @@ -3,15 +3,27 @@ * * Takes in a prop for rendering the content inside the popup */ -import * as React from "react"; +import React, { useEffect } from "react"; interface IProps { content: (props: T) => React.ReactElement; id: string; props: T; + removePopup: (id: string) => void; } export function Popup(props: IProps): React.ReactElement { + function keyDown(event: KeyboardEvent): void { + if(event.key === 'Escape') props.removePopup(props.id); + } + + useEffect(() => { + document.addEventListener('keydown', keyDown); + return () => { + document.removeEventListener('keydown', keyDown); + } + }); + return (
        {React.createElement(props.content, props.props)} diff --git a/src/ui/React/createPopup.tsx b/src/ui/React/createPopup.tsx index 36e1085ea..df24bcfaa 100644 --- a/src/ui/React/createPopup.tsx +++ b/src/ui/React/createPopup.tsx @@ -16,20 +16,27 @@ import { removeElementById } from "../../../utils/uiHelpers/removeElementById"; let gameContainer: HTMLElement; -function getGameContainer(): void { - const container = document.getElementById("entire-game-container"); - if (container == null) { - throw new Error(`Failed to find game container DOM element`) +(function() { + function getGameContainer(): void { + const container = document.getElementById("entire-game-container"); + if (container == null) { + throw new Error(`Failed to find game container DOM element`) + } + + gameContainer = container; + document.removeEventListener("DOMContentLoaded", getGameContainer); } - gameContainer = container; - document.removeEventListener("DOMContentLoaded", getGameContainer); -} + document.addEventListener("DOMContentLoaded", getGameContainer); +})(); -document.addEventListener("DOMContentLoaded", getGameContainer); +// This variable is used to avoid setting the semi-transparent background +// several times on top of one another. Sometimes there's several popup at once. +let deepestPopupId: string = ""; // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types export function createPopup(id: string, rootComponent: (props: T) => React.ReactElement, props: T): HTMLElement | null { + let container = document.getElementById(id); if (container == null) { function onClick(this: HTMLElement, event: MouseEvent): any { @@ -39,19 +46,20 @@ export function createPopup(id: string, rootComponent: (props: T) => React.Re if(clickedId !== id) return; removePopup(id); } + const backgroundColor = deepestPopupId === "" ? 'rgba(0,0,0,0.5)' : 'rgba(0,0,0,0)'; container = createElement("div", { class: "popup-box-container", display: "flex", id: id, - backgroundColor: 'rgba(0,0,0,0.5)', + backgroundColor: backgroundColor, clickListener: onClick, }); gameContainer.appendChild(container); - } - ReactDOM.render(, container); + if(deepestPopupId === "") deepestPopupId = id; + ReactDOM.render(, container); return container; } @@ -67,4 +75,5 @@ export function removePopup(id: string): void { removeElementById(id); removeElementById(`${id}-close`); + if(id === deepestPopupId) deepestPopupId = ""; } diff --git a/utils/DialogBox.d.ts b/utils/DialogBox.d.ts deleted file mode 100644 index 87e1ea85a..000000000 --- a/utils/DialogBox.d.ts +++ /dev/null @@ -1,2 +0,0 @@ -export function dialogBoxCreate(txt: string | JSX.Element, preformatted?: boolean): void; -export let dialogBoxOpened: boolean; diff --git a/utils/DialogBox.jsx b/utils/DialogBox.jsx deleted file mode 100644 index 56d5daa9c..000000000 --- a/utils/DialogBox.jsx +++ /dev/null @@ -1,76 +0,0 @@ -import { KEY } from "./helpers/keyCodes"; -import { DialogBox } from "./ui/DialogBox"; -import React from "react"; -import ReactDOM from "react-dom"; - -/** - * Create and display a pop-up dialog box. - * This dialog box does not allow for any interaction and should close when clicking - * outside of it - */ -let dialogBoxes = []; - -// Close dialog box when clicking outside -$(document).click(function(event) { - if (dialogBoxOpened && dialogBoxes.length >= 1) { - if (!$(event.target).closest(dialogBoxes[0]).length){ - closeTopmostDialogBox(); - } - } -}); - -function closeTopmostDialogBox() { - if (!dialogBoxOpened || dialogBoxes.length === 0) return; - dialogBoxes[0].remove(); - dialogBoxes.shift(); - if (dialogBoxes.length == 0) { - dialogBoxOpened = false; - } else { - dialogBoxes[0].style.visibility = "visible"; - } -} - -// Dialog box close buttons -$(document).on('click', '.dialog-box-close-button', function() { - closeTopmostDialogBox(); -}); - -document.addEventListener("keydown", function (event) { - if (event.keyCode == KEY.ESC && dialogBoxOpened) { - closeTopmostDialogBox(); - event.preventDefault(); - } -}); - -let dialogBoxOpened = false; - - - -function dialogBoxCreate(txt, preformatted=false) { - const container = document.createElement("div"); - container.setAttribute("class", "dialog-box-container"); - - let elem = txt; - if (typeof txt === 'string') { - if (preformatted) { - // For text files as they are often computed data that - // shouldn't be wrapped and should retain tabstops. - elem =
        -        } else {
        -            elem = 

        ') }} /> - } - } - - ReactDOM.render(DialogBox(elem), container); - document.body.appendChild(container); - if (dialogBoxes.length >= 1) { - container.style.visibility = "hidden"; - } - dialogBoxes.push(container); - - setTimeout(function() { - dialogBoxOpened = true; - }, 400); -} - -export {dialogBoxCreate, dialogBoxOpened}; diff --git a/utils/DialogBox.tsx b/utils/DialogBox.tsx new file mode 100644 index 000000000..8d42d471d --- /dev/null +++ b/utils/DialogBox.tsx @@ -0,0 +1,38 @@ +import { KEY } from "./helpers/keyCodes"; +import { DialogBox } from "./ui/DialogBox"; +import { createPopup } from "../src/ui/React/createPopup"; +import { getRandomInt } from "./helpers/getRandomInt"; + +import React from "react"; +import ReactDOM from "react-dom"; + +interface IProps { + content: JSX.Element; +} + +export function MessagePopup(props: IProps): React.ReactElement { + return (<>{props.content}); +} + +function dialogBoxCreate(txt: string | JSX.Element, preformatted: boolean = false): void { + const popupId = `popup-`+(Array.from(Array(16))).map(() => `${getRandomInt(0, 9)}`).join(''); + if (typeof txt === 'string') { + if (preformatted) { + // For text files as they are often computed data that + // shouldn't be wrapped and should retain tabstops. + createPopup(popupId, MessagePopup, { + content: (

        )
        +            });
        +        } else {
        +            createPopup(popupId, MessagePopup, {
        +                content: (

        ') }} />) + }); + } + } else { + createPopup(popupId, MessagePopup, { + content: txt + }); + } +} + +export {dialogBoxCreate}; From 1a1a43c1cea63f892c7946223d31157b8030403c Mon Sep 17 00:00:00 2001 From: Olivier Gagnon Date: Thu, 19 Aug 2021 01:45:26 -0400 Subject: [PATCH 15/15] v0.52.4 --- dist/engine.bundle.js | 4 +- dist/engineStyle.bundle.js | 2 +- dist/vendor.bundle.js | 34 +++--- doc/source/changelog.rst | 49 ++++++++ doc/source/conf.py | 2 +- ...tingstartedguideforbeginnerprogrammers.rst | 2 +- .../getActionCountRemaining.rst | 2 +- .../bladeburnerapi/getActionCurrentLevel.rst | 2 +- index.html | 6 +- package.json | 2 +- src/Augmentation/Augmentation.tsx | 2 +- src/Augmentation/AugmentationHelpers.jsx | 2 - src/Bladeburner/ActionIdentifier.ts | 4 +- src/Bladeburner/Bladeburner.ts | 107 +++++++++++------- src/Bladeburner/IBladeburner.ts | 2 +- src/Bladeburner/ui/BlackOpElem.tsx | 4 +- src/Bladeburner/ui/BlackOpList.tsx | 9 +- src/Bladeburner/ui/Console.tsx | 4 +- src/Bladeburner/ui/ContractElem.tsx | 8 +- src/Bladeburner/ui/ContractList.tsx | 7 +- src/Bladeburner/ui/GeneralActionElem.tsx | 9 +- src/Bladeburner/ui/GeneralActionList.tsx | 6 +- src/Bladeburner/ui/OperationElem.tsx | 10 +- src/Bladeburner/ui/OperationList.tsx | 7 +- src/Bladeburner/ui/SkillElem.tsx | 4 +- src/Bladeburner/ui/SkillPage.tsx | 2 +- src/Bladeburner/ui/Stats.tsx | 4 +- src/Bladeburner/ui/TeamSizePopup.tsx | 1 - src/Bladeburner/ui/TravelPopup.tsx | 8 +- src/Constants.ts | 28 ++--- src/Gang/Gang.ts | 2 - src/Gang/GangMember.ts | 1 - src/Hacknet/HacknetHelpers.jsx | 2 +- src/Hacknet/HashUpgrade.ts | 4 + ...esMetadata.ts => HashUpgradesMetadata.tsx} | 11 ++ src/Hacknet/ui/HashUpgradePopup.jsx | 7 +- src/Infiltration/ui/Intro.tsx | 7 -- src/Script/ScriptHelpers.js | 24 +--- src/engine.jsx | 5 - src/ui/React/createPopup.tsx | 2 +- utils/DialogBox.tsx | 11 +- 41 files changed, 216 insertions(+), 193 deletions(-) rename src/Hacknet/data/{HashUpgradesMetadata.ts => HashUpgradesMetadata.tsx} (70%) diff --git a/dist/engine.bundle.js b/dist/engine.bundle.js index f5da2ea2f..6bc892d23 100644 --- a/dist/engine.bundle.js +++ b/dist/engine.bundle.js @@ -1,4 +1,4 @@ -!function(e){function t(t){for(var a,o,s=t[0],l=t[1],c=t[2],m=0,p=[];m${e}`;r.getElementById("terminal-input").insertAdjacentHTML("beforebegin",a),function(){const e=r.getElementById("terminal-container");e.scrollTop=e.scrollHeight}()}t.post=function(e){i(e)},t.postError=function(e){i(`ERROR: ${e}`,{color:"#ff2929"})},t.hackProgressBarPost=function(e){i(e,{id:"hack-progress-bar"})},t.hackProgressPost=function(e){i(e,{id:"hack-progress"})},t.postElement=function(e){i(a.renderToStaticMarkup(e))},t.postContent=i},,function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.getRamCost=t.RamCosts=t.RamCostConstants=void 0,t.RamCostConstants={ScriptBaseRamCost:1.6,ScriptDomRamCost:25,ScriptHackRamCost:.1,ScriptHackAnalyzeRamCost:1,ScriptGrowRamCost:.15,ScriptGrowthAnalyzeRamCost:1,ScriptWeakenRamCost:.15,ScriptScanRamCost:.2,ScriptPortProgramRamCost:.05,ScriptRunRamCost:1,ScriptExecRamCost:1.3,ScriptSpawnRamCost:2,ScriptScpRamCost:.6,ScriptKillRamCost:.5,ScriptHasRootAccessRamCost:.05,ScriptGetHostnameRamCost:.05,ScriptGetHackingLevelRamCost:.05,ScriptGetMultipliersRamCost:4,ScriptGetServerRamCost:.1,ScriptGetServerMaxRam:.05,ScriptGetServerUsedRam:.05,ScriptFileExistsRamCost:.1,ScriptIsRunningRamCost:.1,ScriptHacknetNodesRamCost:4,ScriptHNUpgLevelRamCost:.4,ScriptHNUpgRamRamCost:.6,ScriptHNUpgCoreRamCost:.8,ScriptGetStockRamCost:2,ScriptBuySellStockRamCost:2.5,ScriptGetPurchaseServerRamCost:.25,ScriptPurchaseServerRamCost:2.25,ScriptGetPurchasedServerLimit:.05,ScriptGetPurchasedServerMaxRam:.05,ScriptRoundRamCost:.05,ScriptReadWriteRamCost:1,ScriptArbScriptRamCost:1,ScriptGetScriptRamCost:.1,ScriptGetRunningScriptRamCost:.3,ScriptGetHackTimeRamCost:.05,ScriptGetFavorToDonate:.1,ScriptCodingContractBaseRamCost:10,ScriptSleeveBaseRamCost:4,ScriptSingularityFn1RamCost:2,ScriptSingularityFn2RamCost:3,ScriptSingularityFn3RamCost:5,ScriptGangApiBaseRamCost:4,ScriptBladeburnerApiBaseRamCost:4},t.RamCosts={hacknet:{numNodes:()=>0,purchaseNode:()=>0,getPurchaseNodeCost:()=>0,getNodeStats:()=>0,upgradeLevel:()=>0,upgradeRam:()=>0,upgradeCore:()=>0,upgradeCache:()=>0,getLevelUpgradeCost:()=>0,getRamUpgradeCost:()=>0,getCoreUpgradeCost:()=>0,getCacheUpgradeCost:()=>0,numHashes:()=>0,hashCost:()=>0,spendHashes:()=>0},sprintf:()=>0,vsprintf:()=>0,scan:()=>t.RamCostConstants.ScriptScanRamCost,hack:()=>t.RamCostConstants.ScriptHackRamCost,hackAnalyzeThreads:()=>t.RamCostConstants.ScriptHackAnalyzeRamCost,hackAnalyzePercent:()=>t.RamCostConstants.ScriptHackAnalyzeRamCost,hackChance:()=>t.RamCostConstants.ScriptHackAnalyzeRamCost,sleep:()=>0,grow:()=>t.RamCostConstants.ScriptGrowRamCost,growthAnalyze:()=>t.RamCostConstants.ScriptGrowthAnalyzeRamCost,weaken:()=>t.RamCostConstants.ScriptWeakenRamCost,print:()=>0,tprint:()=>0,clearLog:()=>0,disableLog:()=>0,enableLog:()=>0,isLogEnabled:()=>0,getScriptLogs:()=>0,nuke:()=>t.RamCostConstants.ScriptPortProgramRamCost,brutessh:()=>t.RamCostConstants.ScriptPortProgramRamCost,ftpcrack:()=>t.RamCostConstants.ScriptPortProgramRamCost,relaysmtp:()=>t.RamCostConstants.ScriptPortProgramRamCost,httpworm:()=>t.RamCostConstants.ScriptPortProgramRamCost,sqlinject:()=>t.RamCostConstants.ScriptPortProgramRamCost,run:()=>t.RamCostConstants.ScriptRunRamCost,exec:()=>t.RamCostConstants.ScriptExecRamCost,spawn:()=>t.RamCostConstants.ScriptSpawnRamCost,kill:()=>t.RamCostConstants.ScriptKillRamCost,killall:()=>t.RamCostConstants.ScriptKillRamCost,exit:()=>0,scp:()=>t.RamCostConstants.ScriptScpRamCost,ls:()=>t.RamCostConstants.ScriptScanRamCost,ps:()=>t.RamCostConstants.ScriptScanRamCost,hasRootAccess:()=>t.RamCostConstants.ScriptHasRootAccessRamCost,getIp:()=>t.RamCostConstants.ScriptGetHostnameRamCost,getHostname:()=>t.RamCostConstants.ScriptGetHostnameRamCost,getHackingLevel:()=>t.RamCostConstants.ScriptGetHackingLevelRamCost,getHackingMultipliers:()=>t.RamCostConstants.ScriptGetMultipliersRamCost,getHacknetMultipliers:()=>t.RamCostConstants.ScriptGetMultipliersRamCost,getBitNodeMultipliers:()=>t.RamCostConstants.ScriptGetMultipliersRamCost,getServer:()=>t.RamCostConstants.ScriptGetMultipliersRamCost/2,getServerMoneyAvailable:()=>t.RamCostConstants.ScriptGetServerRamCost,getServerSecurityLevel:()=>t.RamCostConstants.ScriptGetServerRamCost,getServerBaseSecurityLevel:()=>t.RamCostConstants.ScriptGetServerRamCost,getServerMinSecurityLevel:()=>t.RamCostConstants.ScriptGetServerRamCost,getServerRequiredHackingLevel:()=>t.RamCostConstants.ScriptGetServerRamCost,getServerMaxMoney:()=>t.RamCostConstants.ScriptGetServerRamCost,getServerGrowth:()=>t.RamCostConstants.ScriptGetServerRamCost,getServerNumPortsRequired:()=>t.RamCostConstants.ScriptGetServerRamCost,getServerRam:()=>t.RamCostConstants.ScriptGetServerRamCost,getServerMaxRam:()=>t.RamCostConstants.ScriptGetServerMaxRam,getServerUsedRam:()=>t.RamCostConstants.ScriptGetServerUsedRam,serverExists:()=>t.RamCostConstants.ScriptGetServerRamCost,fileExists:()=>t.RamCostConstants.ScriptFileExistsRamCost,isRunning:()=>t.RamCostConstants.ScriptIsRunningRamCost,getStockSymbols:()=>t.RamCostConstants.ScriptGetStockRamCost,getStockPrice:()=>t.RamCostConstants.ScriptGetStockRamCost,getStockAskPrice:()=>t.RamCostConstants.ScriptGetStockRamCost,getStockBidPrice:()=>t.RamCostConstants.ScriptGetStockRamCost,getStockPosition:()=>t.RamCostConstants.ScriptGetStockRamCost,getStockMaxShares:()=>t.RamCostConstants.ScriptGetStockRamCost,getStockPurchaseCost:()=>t.RamCostConstants.ScriptGetStockRamCost,getStockSaleGain:()=>t.RamCostConstants.ScriptGetStockRamCost,buyStock:()=>t.RamCostConstants.ScriptBuySellStockRamCost,sellStock:()=>t.RamCostConstants.ScriptBuySellStockRamCost,shortStock:()=>t.RamCostConstants.ScriptBuySellStockRamCost,sellShort:()=>t.RamCostConstants.ScriptBuySellStockRamCost,placeOrder:()=>t.RamCostConstants.ScriptBuySellStockRamCost,cancelOrder:()=>t.RamCostConstants.ScriptBuySellStockRamCost,getOrders:()=>t.RamCostConstants.ScriptBuySellStockRamCost,getStockVolatility:()=>t.RamCostConstants.ScriptBuySellStockRamCost,getStockForecast:()=>t.RamCostConstants.ScriptBuySellStockRamCost,purchase4SMarketData:()=>t.RamCostConstants.ScriptBuySellStockRamCost,purchase4SMarketDataTixApi:()=>t.RamCostConstants.ScriptBuySellStockRamCost,getPurchasedServerLimit:()=>t.RamCostConstants.ScriptGetPurchasedServerLimit,getPurchasedServerMaxRam:()=>t.RamCostConstants.ScriptGetPurchasedServerMaxRam,getPurchasedServerCost:()=>t.RamCostConstants.ScriptGetPurchaseServerRamCost,purchaseServer:()=>t.RamCostConstants.ScriptPurchaseServerRamCost,deleteServer:()=>t.RamCostConstants.ScriptPurchaseServerRamCost,getPurchasedServers:()=>t.RamCostConstants.ScriptPurchaseServerRamCost,write:()=>t.RamCostConstants.ScriptReadWriteRamCost,tryWrite:()=>t.RamCostConstants.ScriptReadWriteRamCost,read:()=>t.RamCostConstants.ScriptReadWriteRamCost,peek:()=>t.RamCostConstants.ScriptReadWriteRamCost,clear:()=>t.RamCostConstants.ScriptReadWriteRamCost,getPortHandle:()=>10*t.RamCostConstants.ScriptReadWriteRamCost,rm:()=>t.RamCostConstants.ScriptReadWriteRamCost,scriptRunning:()=>t.RamCostConstants.ScriptArbScriptRamCost,scriptKill:()=>t.RamCostConstants.ScriptArbScriptRamCost,getScriptName:()=>0,getScriptRam:()=>t.RamCostConstants.ScriptGetScriptRamCost,getHackTime:()=>t.RamCostConstants.ScriptGetHackTimeRamCost,getGrowTime:()=>t.RamCostConstants.ScriptGetHackTimeRamCost,getWeakenTime:()=>t.RamCostConstants.ScriptGetHackTimeRamCost,getScriptIncome:()=>t.RamCostConstants.ScriptGetScriptRamCost,getScriptExpGain:()=>t.RamCostConstants.ScriptGetScriptRamCost,getRunningScript:()=>t.RamCostConstants.ScriptGetRunningScriptRamCost,nFormat:()=>0,getTimeSinceLastAug:()=>t.RamCostConstants.ScriptGetHackTimeRamCost,prompt:()=>0,wget:()=>0,getFavorToDonate:()=>t.RamCostConstants.ScriptGetFavorToDonate,universityCourse:()=>t.RamCostConstants.ScriptSingularityFn1RamCost,gymWorkout:()=>t.RamCostConstants.ScriptSingularityFn1RamCost,travelToCity:()=>t.RamCostConstants.ScriptSingularityFn1RamCost,purchaseTor:()=>t.RamCostConstants.ScriptSingularityFn1RamCost,purchaseProgram:()=>t.RamCostConstants.ScriptSingularityFn1RamCost,getCurrentServer:()=>t.RamCostConstants.ScriptSingularityFn1RamCost,connect:()=>t.RamCostConstants.ScriptSingularityFn1RamCost,manualHack:()=>t.RamCostConstants.ScriptSingularityFn1RamCost,installBackdoor:()=>t.RamCostConstants.ScriptSingularityFn1RamCost,getStats:()=>t.RamCostConstants.ScriptSingularityFn1RamCost/4,getCharacterInformation:()=>t.RamCostConstants.ScriptSingularityFn1RamCost/4,getPlayer:()=>t.RamCostConstants.ScriptSingularityFn1RamCost/4,hospitalize:()=>t.RamCostConstants.ScriptSingularityFn1RamCost/4,isBusy:()=>t.RamCostConstants.ScriptSingularityFn1RamCost/4,stopAction:()=>t.RamCostConstants.ScriptSingularityFn1RamCost/2,upgradeHomeRam:()=>t.RamCostConstants.ScriptSingularityFn2RamCost,getUpgradeHomeRamCost:()=>t.RamCostConstants.ScriptSingularityFn2RamCost/2,workForCompany:()=>t.RamCostConstants.ScriptSingularityFn2RamCost,applyToCompany:()=>t.RamCostConstants.ScriptSingularityFn2RamCost,getCompanyRep:()=>t.RamCostConstants.ScriptSingularityFn2RamCost/3,getCompanyFavor:()=>t.RamCostConstants.ScriptSingularityFn2RamCost/3,getCompanyFavorGain:()=>t.RamCostConstants.ScriptSingularityFn2RamCost/4,checkFactionInvitations:()=>t.RamCostConstants.ScriptSingularityFn2RamCost,joinFaction:()=>t.RamCostConstants.ScriptSingularityFn2RamCost,workForFaction:()=>t.RamCostConstants.ScriptSingularityFn2RamCost,getFactionRep:()=>t.RamCostConstants.ScriptSingularityFn2RamCost/3,getFactionFavor:()=>t.RamCostConstants.ScriptSingularityFn2RamCost/3,getFactionFavorGain:()=>t.RamCostConstants.ScriptSingularityFn2RamCost/4,donateToFaction:()=>t.RamCostConstants.ScriptSingularityFn3RamCost,createProgram:()=>t.RamCostConstants.ScriptSingularityFn3RamCost,commitCrime:()=>t.RamCostConstants.ScriptSingularityFn3RamCost,getCrimeChance:()=>t.RamCostConstants.ScriptSingularityFn3RamCost,getCrimeStats:()=>t.RamCostConstants.ScriptSingularityFn3RamCost,getOwnedAugmentations:()=>t.RamCostConstants.ScriptSingularityFn3RamCost,getOwnedSourceFiles:()=>t.RamCostConstants.ScriptSingularityFn3RamCost,getAugmentationsFromFaction:()=>t.RamCostConstants.ScriptSingularityFn3RamCost,getAugmentationPrereq:()=>t.RamCostConstants.ScriptSingularityFn3RamCost,getAugmentationCost:()=>t.RamCostConstants.ScriptSingularityFn3RamCost,getAugmentationStats:()=>t.RamCostConstants.ScriptSingularityFn3RamCost,purchaseAugmentation:()=>t.RamCostConstants.ScriptSingularityFn3RamCost,softReset:()=>t.RamCostConstants.ScriptSingularityFn3RamCost,installAugmentations:()=>t.RamCostConstants.ScriptSingularityFn3RamCost,gang:{createGang:()=>t.RamCostConstants.ScriptGangApiBaseRamCost/4,inGang:()=>t.RamCostConstants.ScriptGangApiBaseRamCost/4,getMemberNames:()=>t.RamCostConstants.ScriptGangApiBaseRamCost/4,getGangInformation:()=>t.RamCostConstants.ScriptGangApiBaseRamCost/2,getOtherGangInformation:()=>t.RamCostConstants.ScriptGangApiBaseRamCost/2,getMemberInformation:()=>t.RamCostConstants.ScriptGangApiBaseRamCost/2,canRecruitMember:()=>t.RamCostConstants.ScriptGangApiBaseRamCost/4,recruitMember:()=>t.RamCostConstants.ScriptGangApiBaseRamCost/2,getTaskNames:()=>t.RamCostConstants.ScriptGangApiBaseRamCost/4,getTaskStats:()=>t.RamCostConstants.ScriptGangApiBaseRamCost/4,setMemberTask:()=>t.RamCostConstants.ScriptGangApiBaseRamCost/2,getEquipmentNames:()=>t.RamCostConstants.ScriptGangApiBaseRamCost/4,getEquipmentCost:()=>t.RamCostConstants.ScriptGangApiBaseRamCost/2,getEquipmentType:()=>t.RamCostConstants.ScriptGangApiBaseRamCost/2,getEquipmentStats:()=>t.RamCostConstants.ScriptGangApiBaseRamCost/2,purchaseEquipment:()=>t.RamCostConstants.ScriptGangApiBaseRamCost,ascendMember:()=>t.RamCostConstants.ScriptGangApiBaseRamCost,setTerritoryWarfare:()=>t.RamCostConstants.ScriptGangApiBaseRamCost/2,getChanceToWinClash:()=>t.RamCostConstants.ScriptGangApiBaseRamCost,getBonusTime:()=>0},bladeburner:{getContractNames:()=>t.RamCostConstants.ScriptBladeburnerApiBaseRamCost/10,getOperationNames:()=>t.RamCostConstants.ScriptBladeburnerApiBaseRamCost/10,getBlackOpNames:()=>t.RamCostConstants.ScriptBladeburnerApiBaseRamCost/10,getBlackOpRank:()=>t.RamCostConstants.ScriptBladeburnerApiBaseRamCost/2,getGeneralActionNames:()=>t.RamCostConstants.ScriptBladeburnerApiBaseRamCost/10,getSkillNames:()=>t.RamCostConstants.ScriptBladeburnerApiBaseRamCost/10,startAction:()=>t.RamCostConstants.ScriptBladeburnerApiBaseRamCost,stopBladeburnerAction:()=>t.RamCostConstants.ScriptBladeburnerApiBaseRamCost/2,getCurrentAction:()=>t.RamCostConstants.ScriptBladeburnerApiBaseRamCost/4,getActionTime:()=>t.RamCostConstants.ScriptBladeburnerApiBaseRamCost,getActionEstimatedSuccessChance:()=>t.RamCostConstants.ScriptBladeburnerApiBaseRamCost,getActionRepGain:()=>t.RamCostConstants.ScriptBladeburnerApiBaseRamCost,getActionCountRemaining:()=>t.RamCostConstants.ScriptBladeburnerApiBaseRamCost,getActionMaxLevel:()=>t.RamCostConstants.ScriptBladeburnerApiBaseRamCost,getActionCurrentLevel:()=>t.RamCostConstants.ScriptBladeburnerApiBaseRamCost,getActionAutolevel:()=>t.RamCostConstants.ScriptBladeburnerApiBaseRamCost,setActionAutolevel:()=>t.RamCostConstants.ScriptBladeburnerApiBaseRamCost,setActionLevel:()=>t.RamCostConstants.ScriptBladeburnerApiBaseRamCost,getRank:()=>t.RamCostConstants.ScriptBladeburnerApiBaseRamCost,getSkillPoints:()=>t.RamCostConstants.ScriptBladeburnerApiBaseRamCost,getSkillLevel:()=>t.RamCostConstants.ScriptBladeburnerApiBaseRamCost,getSkillUpgradeCost:()=>t.RamCostConstants.ScriptBladeburnerApiBaseRamCost,upgradeSkill:()=>t.RamCostConstants.ScriptBladeburnerApiBaseRamCost,getTeamSize:()=>t.RamCostConstants.ScriptBladeburnerApiBaseRamCost,setTeamSize:()=>t.RamCostConstants.ScriptBladeburnerApiBaseRamCost,getCityEstimatedPopulation:()=>t.RamCostConstants.ScriptBladeburnerApiBaseRamCost,getCityEstimatedCommunities:()=>t.RamCostConstants.ScriptBladeburnerApiBaseRamCost,getCityChaos:()=>t.RamCostConstants.ScriptBladeburnerApiBaseRamCost,getCity:()=>t.RamCostConstants.ScriptBladeburnerApiBaseRamCost,switchCity:()=>t.RamCostConstants.ScriptBladeburnerApiBaseRamCost,getStamina:()=>t.RamCostConstants.ScriptBladeburnerApiBaseRamCost,joinBladeburnerFaction:()=>t.RamCostConstants.ScriptBladeburnerApiBaseRamCost,joinBladeburnerDivision:()=>t.RamCostConstants.ScriptBladeburnerApiBaseRamCost,getBonusTime:()=>0},codingcontract:{attempt:()=>t.RamCostConstants.ScriptCodingContractBaseRamCost,getContractType:()=>t.RamCostConstants.ScriptCodingContractBaseRamCost/2,getData:()=>t.RamCostConstants.ScriptCodingContractBaseRamCost/2,getDescription:()=>t.RamCostConstants.ScriptCodingContractBaseRamCost/2,getNumTriesRemaining:()=>t.RamCostConstants.ScriptCodingContractBaseRamCost/5},sleeve:{getNumSleeves:()=>t.RamCostConstants.ScriptSleeveBaseRamCost,setToShockRecovery:()=>t.RamCostConstants.ScriptSleeveBaseRamCost,setToSynchronize:()=>t.RamCostConstants.ScriptSleeveBaseRamCost,setToCommitCrime:()=>t.RamCostConstants.ScriptSleeveBaseRamCost,setToUniversityCourse:()=>t.RamCostConstants.ScriptSleeveBaseRamCost,travel:()=>t.RamCostConstants.ScriptSleeveBaseRamCost,setToCompanyWork:()=>t.RamCostConstants.ScriptSleeveBaseRamCost,setToFactionWork:()=>t.RamCostConstants.ScriptSleeveBaseRamCost,setToGymWorkout:()=>t.RamCostConstants.ScriptSleeveBaseRamCost,getSleeveStats:()=>t.RamCostConstants.ScriptSleeveBaseRamCost,getTask:()=>t.RamCostConstants.ScriptSleeveBaseRamCost,getInformation:()=>t.RamCostConstants.ScriptSleeveBaseRamCost,getSleeveAugmentations:()=>t.RamCostConstants.ScriptSleeveBaseRamCost,getSleevePurchasableAugs:()=>t.RamCostConstants.ScriptSleeveBaseRamCost,purchaseSleeveAug:()=>t.RamCostConstants.ScriptSleeveBaseRamCost},heart:{break:()=>0}},t.getRamCost=function(...e){if(0===e.length)return console.warn("No arguments passed to getRamCost()"),0;let n=t.RamCosts[e[0]];for(let t=1;t
        In this game you control a set of Nodes and use them to try and defeat an enemy. Your Nodes are colored blue, while the enemy's are red. There are also other nodes on the map colored gray that initially belong to neither you nor the enemy. The goal of the game is to capture all of the enemy's Database nodes within the time limit. If you fail to do this, you will lose.

        Each Node has three stats: Attack, Defense, and HP. There are five different actions that a Node can take:

        Attack - Targets an enemy Node and lowers its HP. The effectiveness is determined by the owner's Attack, the Player's hacking level, and the enemy's defense.

        Scan - Targets an enemy Node and lowers its Defense. The effectiveness is determined by the owner's Attack, the Player's hacking level, and the enemy's defense.

        Weaken - Targets an enemy Node and lowers its Attack. The effectiveness is determined by the owner's Attack, the Player's hacking level, and the enemy's defense.

        Fortify - Raises the Node's Defense. The effectiveness is determined by your hacking level.

        Overflow - Raises the Node's Attack but lowers its Defense. The effectiveness is determined by your hacking level.

        Note that when determining the effectiveness of the above actions, the TOTAL Attack or Defense of the team is used, not just the Attack/Defense of the individual Node that is performing the action.

        To capture a Node, you must lower its HP down to 0.

        There are six different types of Nodes:

        CPU Core - These are your main Nodes that are used to perform actions. Capable of performing every action

        Firewall - Nodes with high defense. These Nodes can 'Fortify'

        Database - A special type of Node. The player's objective is to conquer all of the enemy's Database Nodes within the time limit. These Nodes cannot perform any actions

        Spam - Conquering one of these Nodes will slow the enemy's trace, giving the player additional time to complete the mission. These Nodes cannot perform any actions

        Transfer - Conquering one of these nodes will increase the Attack of all of your CPU Cores by a small fixed percentage. These Nodes are capable of performing every action except the 'Attack' action

        Shield - Nodes with high defense. These Nodes can 'Fortify'

        To assign an action to a Node, you must first select one of your Nodes. This can be done by simply clicking on it. Double-clicking a node will select all of your Nodes of the same type (e.g. select all CPU Core Nodes or all Transfer Nodes). Note that only Nodes that can perform actions (CPU Core, Transfer, Shield, Firewall) can be selected. Selected Nodes will be denoted with a white highlight. After selecting a Node or multiple Nodes, select its action using the Action Buttons near the top of the screen. Every action also has a corresponding keyboard shortcut.

        For certain actions such as attacking, scanning, and weakening, the Node performing the action must have a target. To target another node, simply click-and-drag from the 'source' Node to a target. A Node can only have one target, and you can target any Node that is adjacent to one of your Nodes (immediately above, below, or to the side. NOT diagonal). Furthermore, only CPU Cores and Transfer Nodes can target, since they are the only ones that can perform the related actions. To remove a target, you can simply click on the line that represents the connection between one of your Nodes and its target. Alternatively, you can select the 'source' Node and click the 'Drop Connection' button, or press 'd'.

        Other Notes:

        -Whenever a miscellenaous Node (not owned by the player or enemy) is conquered, the defense of all remaining miscellaneous Nodes that are not actively being targeted will increase by a fixed percentage.

        -Whenever a Node is conquered, its stats are significantly reduced

        -Miscellaneous Nodes slowly raise their defense over time

        -Nodes slowly regenerate health over time.",MillisecondsPer20Hours:72e6,GameCyclesPer20Hours:36e4,MillisecondsPer10Hours:36e6,GameCyclesPer10Hours:18e4,MillisecondsPer8Hours:288e5,GameCyclesPer8Hours:144e3,MillisecondsPer4Hours:144e5,GameCyclesPer4Hours:72e3,MillisecondsPer2Hours:72e5,GameCyclesPer2Hours:36e3,MillisecondsPerHour:36e5,GameCyclesPerHour:18e3,MillisecondsPerHalfHour:18e5,GameCyclesPerHalfHour:9e3,MillisecondsPerQuarterHour:9e5,GameCyclesPerQuarterHour:4500,MillisecondsPerFiveMinutes:3e5,GameCyclesPerFiveMinutes:1500,FactionWorkHacking:"Faction Hacking Work",FactionWorkField:"Faction Field Work",FactionWorkSecurity:"Faction Security Work",WorkTypeCompany:"Working for Company",WorkTypeCompanyPartTime:"Working for Company part-time",WorkTypeFaction:"Working for Faction",WorkTypeCreateProgram:"Working on Create a Program",WorkTypeStudyClass:"Studying or Taking a class at university",WorkTypeCrime:"Committing a crime",ClassStudyComputerScience:"studying Computer Science",ClassDataStructures:"taking a Data Structures course",ClassNetworks:"taking a Networks course",ClassAlgorithms:"taking an Algorithms course",ClassManagement:"taking a Management course",ClassLeadership:"taking a Leadership course",ClassGymStrength:"training your strength at a gym",ClassGymDefense:"training your defense at a gym",ClassGymDexterity:"training your dexterity at a gym",ClassGymAgility:"training your agility at a gym",ClassDataStructuresBaseCost:40,ClassNetworksBaseCost:80,ClassAlgorithmsBaseCost:320,ClassManagementBaseCost:160,ClassLeadershipBaseCost:320,ClassGymBaseCost:120,ClassStudyComputerScienceBaseExp:.5,ClassDataStructuresBaseExp:1,ClassNetworksBaseExp:2,ClassAlgorithmsBaseExp:4,ClassManagementBaseExp:2,ClassLeadershipBaseExp:4,CrimeShoplift:"shoplift",CrimeRobStore:"rob a store",CrimeMug:"mug someone",CrimeLarceny:"commit larceny",CrimeDrugs:"deal drugs",CrimeBondForgery:"forge corporate bonds",CrimeTraffickArms:"traffick illegal arms",CrimeHomicide:"commit homicide",CrimeGrandTheftAuto:"commit grand theft auto",CrimeKidnap:"kidnap someone for ransom",CrimeAssassination:"assassinate a high-profile target",CrimeHeist:"pull off the ultimate heist",CodingContractBaseFactionRepGain:2500,CodingContractBaseCompanyRepGain:4e3,CodingContractBaseMoneyGain:75e6,TotalNumBitNodes:24,LatestUpdate:'\n v0.52.3 - 2021-07-15 Gangs were OP (hydroflame)\n -------------------------------------------\n\n ** Gang **\n\n * Significant rework. Ascension is now based on exp gained.\n * All upgrades give exp bonuses.\n * Maximum gang members reduced to 12.\n * Respect required to recruit sharply increased.\n * Rewritten in React, the UI should be smoother and less laggy now.\n\n ** Infiltration **\n\n * Now isTrusted protected.\n\n ** Misc. **\n\n * Many UI element are now "noselect" protected.\n * Fixed an issue where you could join the same faction twice via script and\n UI simultaneously.\n * Factions list screen converted to React.\n'}},function(e,t,n){"use strict";n.r(t),function(e){n.d(t,"dialogBoxCreate",function(){return p}),n.d(t,"dialogBoxOpened",function(){return m});var a=n(40),r=n(792),i=n(0),o=n.n(i),s=n(25),l=n.n(s);let c=[];function u(){m&&0!==c.length&&(c[0].remove(),c.shift(),0==c.length?m=!1:c[0].style.visibility="visible")}e(document).click(function(t){m&&c.length>=1&&(e(t.target).closest(c[0]).length||u())}),e(document).on("click",".dialog-box-close-button",function(){u()}),document.addEventListener("keydown",function(e){e.keyCode==a.KEY.ESC&&m&&(u(),e.preventDefault())});let m=!1;function p(e,t=!1){const n=document.createElement("div");n.setAttribute("class","dialog-box-container");let a=e;"string"==typeof e&&(a=t?o.a.createElement("pre",{dangerouslySetInnerHTML:{__html:e}}):o.a.createElement("p",{dangerouslySetInnerHTML:{__html:e.replace(/(?:\r\n|\r|\n)/g,"
        ")}})),l.a.render(Object(r.DialogBox)(a),n),document.body.appendChild(n),c.length>=1&&(n.style.visibility="hidden"),c.push(n),setTimeout(function(){m=!0},400)}}.call(this,n(129))},,function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.replaceAt=t.generateRandomString=t.isHTML=t.formatNumber=t.containsAllStrings=t.longestCommonStart=t.convertTimeMsToTimeElapsedString=void 0;const a=n(65);function r(e){return e.every(a.isString)}t.replaceAt=function(e,t,n){return e.substr(0,t)+n+e.substr(t+n.length)},t.convertTimeMsToTimeElapsedString=function(e,t=!1){e=Math.floor(e);const n=Math.floor(e/1e3),a=Math.floor(n/86400),r=n%86400,i=Math.floor(r/3600),o=r%3600,s=Math.floor(o/60),l=o%60,c=(()=>{let t=`${e%1e3}`;for(;t.length<3;)t="0"+t;return t})(),u=t?`${l}.${c}`:`${l}`;let m="";return a>0&&(m+=`${a} days `),i>0&&(m+=`${i} hours `),s>0&&(m+=`${s} minutes `),m+=`${u} seconds`},t.longestCommonStart=function(e){if(!r(e))return"";if(0===e.length)return"";const t=e.concat().sort(),n=t[0],a=t[t.length-1],i=n.length;let o=0;const s=(e,t)=>e.toUpperCase()===t.toUpperCase();for(;o=0;e--)if(1===n[e].nodeType)return!0;return!1},t.generateRandomString=function(e){let t="";const n="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";for(let a=0;a0&&(de._lastUpdate=e-n,R.Player.lastUpdate=e-n,de.updateGame(t)),window.requestAnimationFrame(de.idleTimer)},updateGame:function(e=1){var t=e*de._idleSpeed;null==R.Player.totalPlaytime&&(R.Player.totalPlaytime=0),null==R.Player.playtimeSinceLastAug&&(R.Player.playtimeSinceLastAug=0),null==R.Player.playtimeSinceLastBitnode&&(R.Player.playtimeSinceLastBitnode=0),R.Player.totalPlaytime+=t,R.Player.playtimeSinceLastAug+=t,R.Player.playtimeSinceLastBitnode+=t,!0===K.a.actionStarted&&(de._totalActionTime=K.a.actionTime,de._actionTimeLeft=K.a.actionTime,de._actionInProgress=!0,de._actionProgressBarCount=1,de._actionProgressStr="[ ]",de._actionTimeStr="Time left: ",K.a.actionStarted=!1),R.Player.isWorking&&(R.Player.workType==_.CONSTANTS.WorkTypeFaction?R.Player.workForFaction(e):R.Player.workType==_.CONSTANTS.WorkTypeCreateProgram?R.Player.createProgramWork(e):R.Player.workType==_.CONSTANTS.WorkTypeStudyClass?R.Player.takeClass(e):R.Player.workType==_.CONSTANTS.WorkTypeCrime?R.Player.commitCrime(e):R.Player.workType==_.CONSTANTS.WorkTypeCompanyPartTime?R.Player.workPartTime(e):R.Player.work(e)),R.Player.hasWseAccount&&Object(H.processStockPrices)(e),R.Player.inGang()&&R.Player.gang.process(e,R.Player),x.c&&x.b&&x.b.process(e),R.Player.corporation instanceof h.c&&R.Player.corporation.storeCycles(e),R.Player.bladeburner instanceof c.a&&R.Player.bladeburner.storeCycles(e);for(let t=0;t0?(t.innerHTML=e,t.setAttribute("class","notification-on")):(t.innerHTML="",t.setAttribute("class","notification-off")),de.Counters.createProgramNotifications=10}if(de.Counters.augmentationsNotifications<=0){e=R.Player.queuedAugmentations.length,t=document.getElementById("augmentations-notification");e>0?(t.innerHTML=e,t.setAttribute("class","notification-on")):(t.innerHTML="",t.setAttribute("class","notification-off")),de.Counters.augmentationsNotifications=10}if(de.Counters.checkFactionInvitations<=0){var n=R.Player.checkForFactionInvitations();if(n.length>0){!1===R.Player.firstFacInvRecvd&&(R.Player.firstFacInvRecvd=!0,document.getElementById("factions-tab").style.display="list-item",document.getElementById("character-menu-header").click(),document.getElementById("character-menu-header").click());var a=n[Math.floor(Math.random()*n.length)];Object(y.inviteToFaction)(a)}const e=R.Player.factionInvitations.length,t=document.getElementById("factions-notification");e>0?(t.innerHTML=e,t.setAttribute("class","notification-on")):(t.innerHTML="",t.setAttribute("class","notification-off")),de.Counters.checkFactionInvitations=100}if(de.Counters.passiveFactionGrowth<=0){var o=Math.floor(5-de.Counters.passiveFactionGrowth);Object(y.processPassiveFactionRepGain)(o),de.Counters.passiveFactionGrowth=5}if(de.Counters.messages<=0&&(Object(T.b)(),i.Augmentations[s.AugmentationNames.TheRedPill].owned?de.Counters.messages=4500:de.Counters.messages=150),de.Counters.mechanicProcess<=0){if(R.Player.corporation instanceof h.c&&R.Player.corporation.process(),R.Player.bladeburner instanceof c.a)try{R.Player.bladeburner.process()}catch(e){Object(oe.exceptionAlert)("Exception caught in Bladeburner.process(): "+e)}de.Counters.mechanicProcess=5}de.Counters.contractGeneration<=0&&(Math.random()<=.25&&Object(p.generateRandomContract)(),de.Counters.contractGeneration=3e3)},_totalActionTime:0,_actionTimeLeft:0,_actionTimeStr:"Time left: ",_actionProgressStr:"[ ]",_actionProgressBarCount:1,_actionInProgress:!1,updateHackProgress:function(e=1){var t=e*de._idleSpeed;de._actionTimeLeft-=t/1e3,de._actionTimeLeft=Math.max(de._actionTimeLeft,0);for(var n=Math.round(100*(1-de._actionTimeLeft/de._totalActionTime));2*de._actionProgressBarCount<=n;)de._actionProgressStr=Object(a.replaceAt)(de._actionProgressStr,de._actionProgressBarCount,"|"),de._actionProgressBarCount+=1;de._actionTimeStr="Time left: "+Math.max(0,Math.round(de._actionTimeLeft)).toString()+"s",document.getElementById("hack-progress").innerHTML=de._actionTimeStr,document.getElementById("hack-progress-bar").innerHTML=de._actionProgressStr.replace(/ /g," "),n>=100&&(de._actionInProgress=!1,K.a.finishAction())},closeMainMenuHeader:function(e){for(var t=0;te===1.0777-1?"7.77%":e===1.777-1?"77.7%":c.numeralWrapper.formatPercentage(e,t);let r=o.createElement(o.Fragment,null,"Effects:");return e.hacking_mult&&e.hacking_mult==e.strength_mult&&e.hacking_mult==e.defense_mult&&e.hacking_mult==e.dexterity_mult&&e.hacking_mult==e.agility_mult&&e.hacking_mult==e.charisma_mult?r=o.createElement(o.Fragment,null,r,o.createElement("br",null),"+",a(e.hacking_mult-1)," all skills"):(e.hacking_mult&&(r=o.createElement(o.Fragment,null,r,o.createElement("br",null),"+",a(e.hacking_mult-1)," hacking skill")),e.strength_mult&&e.strength_mult==e.defense_mult&&e.strength_mult==e.dexterity_mult&&e.strength_mult==e.agility_mult?r=o.createElement(o.Fragment,null,r,o.createElement("br",null),"+",a(e.strength_mult-1)," combat skills"):(e.strength_mult&&(r=o.createElement(o.Fragment,null,r,o.createElement("br",null),"+",a(e.strength_mult-1)," strength skill")),e.defense_mult&&(r=o.createElement(o.Fragment,null,r,o.createElement("br",null),"+",a(e.defense_mult-1)," defense skill")),e.dexterity_mult&&(r=o.createElement(o.Fragment,null,r,o.createElement("br",null),"+",a(e.dexterity_mult-1)," dexterity skill")),e.agility_mult&&(r=o.createElement(o.Fragment,null,r,o.createElement("br",null),"+",a(e.agility_mult-1)," agility skill"))),e.charisma_mult&&(r=o.createElement(o.Fragment,null,r,o.createElement("br",null),"+",a(e.charisma_mult-1)," Charisma skill"))),e.hacking_exp_mult&&e.hacking_exp_mult===e.strength_exp_mult&&e.hacking_exp_mult===e.defense_exp_mult&&e.hacking_exp_mult===e.dexterity_exp_mult&&e.hacking_exp_mult===e.agility_exp_mult&&e.hacking_exp_mult===e.charisma_exp_mult?r=o.createElement(o.Fragment,null,r,o.createElement("br",null),"+",a(e.hacking_exp_mult-1)," exp for all skills"):(e.hacking_exp_mult&&(r=o.createElement(o.Fragment,null,r,o.createElement("br",null),"+",a(e.hacking_exp_mult-1)," hacking exp")),e.strength_exp_mult&&e.strength_exp_mult===e.defense_exp_mult&&e.strength_exp_mult===e.dexterity_exp_mult&&e.strength_exp_mult===e.agility_exp_mult?r=o.createElement(o.Fragment,null,r,o.createElement("br",null),"+",a(e.strength_exp_mult-1)," combat exp"):(e.strength_exp_mult&&(r=o.createElement(o.Fragment,null,r,o.createElement("br",null),"+",a(e.strength_exp_mult-1)," strength exp")),e.defense_exp_mult&&(r=o.createElement(o.Fragment,null,r,o.createElement("br",null),"+",a(e.defense_exp_mult-1)," defense exp")),e.dexterity_exp_mult&&(r=o.createElement(o.Fragment,null,r,o.createElement("br",null),"+",a(e.dexterity_exp_mult-1)," dexterity exp")),e.agility_exp_mult&&(r=o.createElement(o.Fragment,null,r,o.createElement("br",null),"+",a(e.agility_exp_mult-1)," agility exp"))),e.charisma_exp_mult&&(r=o.createElement(o.Fragment,null,r,o.createElement("br",null),"+",a(e.charisma_exp_mult-1)," charisma exp"))),e.hacking_speed_mult&&(r=o.createElement(o.Fragment,null,r,o.createElement("br",null),"+",a(e.hacking_speed_mult-1)," faster hacking")),e.hacking_chance_mult&&(r=o.createElement(o.Fragment,null,r,o.createElement("br",null),"+",a(e.hacking_chance_mult-1)," hack() success chance")),e.hacking_money_mult&&(r=o.createElement(o.Fragment,null,r,o.createElement("br",null),"+",a(e.hacking_money_mult-1)," hack() power")),e.hacking_grow_mult&&(r=o.createElement(o.Fragment,null,r,o.createElement("br",null),"+",a(e.hacking_grow_mult-1)," grow() power")),e.faction_rep_mult&&e.faction_rep_mult===e.company_rep_mult?r=o.createElement(o.Fragment,null,r,o.createElement("br",null),"+",a(e.faction_rep_mult-1)," reputation from factions and companies"):(e.faction_rep_mult&&(r=o.createElement(o.Fragment,null,r,o.createElement("br",null),"+",a(e.faction_rep_mult-1)," reputation from factions")),e.company_rep_mult&&(r=o.createElement(o.Fragment,null,r,o.createElement("br",null),"+",a(e.company_rep_mult-1)," reputation from companies"))),e.crime_money_mult&&(r=o.createElement(o.Fragment,null,r,o.createElement("br",null),"+",a(e.crime_money_mult-1)," crime money")),e.crime_success_mult&&(r=o.createElement(o.Fragment,null,r,o.createElement("br",null),"+",a(e.crime_success_mult-1)," crime success rate")),e.work_money_mult&&(r=o.createElement(o.Fragment,null,r,o.createElement("br",null),"+",a(e.work_money_mult-1)," work money")),e.hacknet_node_money_mult&&(r=o.createElement(o.Fragment,null,r,o.createElement("br",null),"+",a(e.hacknet_node_money_mult-1)," hacknet production")),e.hacknet_node_purchase_cost_mult&&(r=o.createElement(o.Fragment,null,r,o.createElement("br",null),"-",a(-(e.hacknet_node_purchase_cost_mult-1))," hacknet nodes cost")),e.hacknet_node_level_cost_mult&&(r=o.createElement(o.Fragment,null,r,o.createElement("br",null),"-",a(-(e.hacknet_node_level_cost_mult-1))," hacknet nodes upgrade cost")),e.bladeburner_max_stamina_mult&&(r=o.createElement(o.Fragment,null,r,o.createElement("br",null),"+",a(e.bladeburner_max_stamina_mult-1)," Bladeburner Max Stamina")),e.bladeburner_stamina_gain_mult&&(r=o.createElement(o.Fragment,null,r,o.createElement("br",null),"+",a(e.bladeburner_stamina_gain_mult-1)," Bladeburner Stamina gain")),e.bladeburner_analysis_mult&&(r=o.createElement(o.Fragment,null,r,o.createElement("br",null),"+",a(e.bladeburner_analysis_mult-1)," Bladeburner Field Analysis effectiveness")),e.bladeburner_success_chance_mult&&(r=o.createElement(o.Fragment,null,r,o.createElement("br",null),"+",a(e.bladeburner_success_chance_mult-1)," Bladeburner Contracts and Operations success chance")),n&&(r=o.createElement(o.Fragment,null,r,o.createElement("br",null),"Start with ",u.Money(n)," after installing Augmentations.")),t&&(r=o.createElement(o.Fragment,null,r,o.createElement("br",null),"Start with ",t.join(" and ")," after installing Augmentations.")),r}(this.mults,e.programs,e.startingMoney)}addToFactions(e){for(let t=0;t{switch(typeof e){case"number":return e;case"object":return s.getRandomInt(e.min,e.max);default:throw Error(`Do not know how to convert the type '${typeof e}' to a number`)}};for(const e of i.serverMetadata){const i={hostname:e.hostname,ip:u(),numOpenPortsRequired:e.numOpenPortsRequired,organizationName:e.organizationName};void 0!==e.maxRamExponent&&(i.maxRam=Math.pow(2,o(e.maxRamExponent)));for(const t of n)void 0!==e[t]&&(i[t]=o(e[t]));const s=new a.Server(i);for(const t of e.literature||[])s.messages.push(t);void 0!==e.specialName&&r.SpecialServerIps.addIp(e.specialName,s.ip),m(s),void 0!==e.networkLayer&&t[o(e.networkLayer)-1].push(s)}const l=(e,t)=>{e.serversOnNetwork.push(t.ip),t.serversOnNetwork.push(e.ip)},c=e=>e[Math.floor(Math.random()*e.length)],p=(e,t)=>{for(const n of e)l(n,t())};p(t[0],()=>e);for(let e=1;ec(t[e-1]))},t.prestigeAllServers=function(){for(const e in t.AllServers)delete t.AllServers[e];t.AllServers={}},t.loadAllServers=function(e){t.AllServers=JSON.parse(e,l.Reviver)}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.EmployeePositions=void 0,t.EmployeePositions={Operations:"Operations",Engineer:"Engineer",Business:"Business",Management:"Management",RandD:"Research & Development",Training:"Training",Unassigned:"Unassigned"}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.isBackdoorInstalled=t.getServerOnNetwork=t.getServer=t.GetServerByHostname=t.prestigeHomeComputer=t.processSingleServerGrowth=t.numCycleForGrowth=t.safetlyCreateUniqueServer=void 0;const a=n(27),r=n(495),i=n(525),o=n(24),s=n(11),l=n(34),c=n(148),u=n(1199),m=n(686);function p(e,t,n){let a=1+(s.CONSTANTS.ServerBaseGrowthRate-1)/e.hackDifficulty;a>s.CONSTANTS.ServerMaxGrowthRate&&(a=s.CONSTANTS.ServerMaxGrowthRate);const r=e.serverGrowth/100;return Math.log(t)/(Math.log(a)*n.hacking_grow_mult*r*o.BitNodeMultipliers.ServerGrowthRate)}function d(e){for(const t in a.AllServers)if(a.AllServers.hasOwnProperty(t)&&a.AllServers[t].hostname==e)return a.AllServers[t];return null}t.safetlyCreateUniqueServer=function(e){if(null!=e.ip&&a.ipExists(e.ip)&&(e.ip=a.createUniqueRandomIp()),null!=d(e.hostname)){let t=e.hostname;for(let n=0;n<200&&null!=d(t=`${e.hostname}-${n}`);++n);e.hostname=t}return new r.Server(e)},t.numCycleForGrowth=p,t.processSingleServerGrowth=function(e,t,n){let a=i.calculateServerGrowth(e,t,n);a<1&&(console.warn("serverGrowth calculated to be less than 1"),a=1);const r=e.moneyAvailable;if(e.moneyAvailable*=a,u.isValidNumber(e.moneyMax)&&isNaN(e.moneyAvailable)&&(e.moneyAvailable=e.moneyMax),u.isValidNumber(e.moneyMax)&&e.moneyAvailable>e.moneyMax&&(e.moneyAvailable=e.moneyMax),r!==e.moneyAvailable){let t=p(e,e.moneyAvailable/r,n);t=Math.max(0,t),e.fortify(2*s.CONSTANTS.ServerFortifyAmount*Math.ceil(t))}return e.moneyAvailable/r},t.prestigeHomeComputer=function(e){const t=e.programs.includes(l.Programs.BitFlume.name);e.programs.length=0,e.runningScripts=[],e.serversOnNetwork=[],e.isConnectedTo=!0,e.ramUsed=0,e.programs.push(l.Programs.NukeProgram.name),t&&e.programs.push(l.Programs.BitFlume.name),e.scripts.forEach(function(t){t.updateRamUsage(e.scripts)}),e.messages.length=0,e.messages.push(c.LiteratureNames.HackersStartingHandbook)},t.GetServerByHostname=d,t.getServer=function(e){return m.isValidIPAddress(e)?void 0!==a.AllServers[e]?a.AllServers[e]:null:d(e)},t.getServerOnNetwork=function(e,t){return t>e.serversOnNetwork.length?(console.error("Tried to get server on network that was out of range"),null):a.AllServers[e.serversOnNetwork[t]]},t.isBackdoorInstalled=function(e){return"backdoorInstalled"in e&&e.backdoorInstalled}},function(e,t,n){"use strict";n.d(t,"h",function(){return C}),n.d(t,"m",function(){return P}),n.d(t,"i",function(){return S}),n.d(t,"b",function(){return O}),n.d(t,"c",function(){return M}),n.d(t,"f",function(){return T}),n.d(t,"g",function(){return x}),n.d(t,"e",function(){return w}),n.d(t,"d",function(){return A}),n.d(t,"o",function(){return R}),n.d(t,"p",function(){return N}),n.d(t,"l",function(){return D}),n.d(t,"k",function(){return B}),n.d(t,"q",function(){return I}),n.d(t,"a",function(){return L}),n.d(t,"j",function(){return j}),n.d(t,"r",function(){return F}),n.d(t,"n",function(){return U});var a=n(540),r=n(163),i=n(155),o=n(38),s=n(89),l=n(185),c=n(252),u=n(184),m=n(51),p=n(1),d=n(27),h=n(29),_=n(46),g=n(16),f=n(0),y=n.n(f),b=n(25),E=n.n(b),v=n(453);let k;function C(){return 9===p.Player.bitNodeN||_.SourceFileFlags[9]>0}function P(){if(m.a.isRunning){if(m.a.currStep!==m.d.HacknetNodesIntroduction)return;Object(m.b)()}const e=p.Player.hacknetNodes.length;if(C()){const t=M();if(isNaN(t))throw new Error("Calculated cost of purchasing HacknetServer is NaN");return p.Player.canAfford(t)?(p.Player.loseMoney(t),p.Player.createHacknetServer(),F(),e):-1}{const t=O();if(isNaN(t))throw new Error("Calculated cost of purchasing HacknetNode is NaN");if(!p.Player.canAfford(t))return-1;const n="hacknet-node-"+e,r=new a.HacknetNode(n,p.Player.hacknet_node_money_mult);return p.Player.loseMoney(t),p.Player.hacknetNodes.push(r),e}}function S(){return C()&&p.Player.hacknetNodes.length>=o.HacknetServerConstants.MaxServers}function O(){return Object(r.calculateNodeCost)(p.Player.hacknetNodes.length+1,p.Player.hacknet_node_purchase_cost_mult)}function M(){return Object(i.calculateServerCost)(p.Player.hacknetNodes.length+1,p.Player.hacknet_node_purchase_cost_mult)}function T(e,t){if(null==t)throw new Error("getMaxNumberLevelUpgrades() called without maxLevel arg");if(p.Player.money.lt(e.calculateLevelUpgradeCost(1,p.Player.hacknet_node_level_cost_mult)))return 0;let n=1,a=t-1,r=t-e.level;if(p.Player.money.gt(e.calculateLevelUpgradeCost(r,p.Player.hacknet_node_level_cost_mult)))return r;for(;n<=a;){var i=(n+a)/2|0;if(i!==t&&p.Player.money.gt(e.calculateLevelUpgradeCost(i,p.Player.hacknet_node_level_cost_mult))&&p.Player.money.lt(e.calculateLevelUpgradeCost(i+1,p.Player.hacknet_node_level_cost_mult)))return Math.min(r,i);if(p.Player.money.lt(e.calculateLevelUpgradeCost(i,p.Player.hacknet_node_level_cost_mult)))a=i-1;else{if(!p.Player.money.gt(e.calculateLevelUpgradeCost(i,p.Player.hacknet_node_level_cost_mult)))return Math.min(r,i);n=i+1}}return 0}function x(e,t){if(null==t)throw new Error("getMaxNumberRamUpgrades() called without maxLevel arg");if(p.Player.money.lt(e.calculateRamUpgradeCost(1,p.Player.hacknet_node_ram_cost_mult)))return 0;let n;if(n=e instanceof s.HacknetServer?Math.round(Math.log2(t/e.maxRam)):Math.round(Math.log2(t/e.ram)),p.Player.money.gt(e.calculateRamUpgradeCost(n,p.Player.hacknet_node_ram_cost_mult)))return n;for(let t=n-1;t>=0;--t)if(p.Player.money.gt(e.calculateRamUpgradeCost(t,p.Player.hacknet_node_ram_cost_mult)))return t;return 0}function w(e,t){if(null==t)throw new Error("getMaxNumberCoreUpgrades() called without maxLevel arg");if(p.Player.money.lt(e.calculateCoreUpgradeCost(1,p.Player.hacknet_node_core_cost_mult)))return 0;let n=1,a=t-1;const r=t-e.cores;if(p.Player.money.gt(e.calculateCoreUpgradeCost(r,p.Player.hacknet_node_core_cost_mult)))return r;for(;n<=a;){let i=(n+a)/2|0;if(i!=t&&p.Player.money.gt(e.calculateCoreUpgradeCost(i,p.Player.hacknet_node_core_cost_mult))&&p.Player.money.lt(e.calculateCoreUpgradeCost(i+1,p.Player.hacknet_node_core_cost_mult)))return Math.min(r,i);if(p.Player.money.lt(e.calculateCoreUpgradeCost(i,p.Player.hacknet_node_core_cost_mult)))a=i-1;else{if(!p.Player.money.gt(e.calculateCoreUpgradeCost(i,p.Player.hacknet_node_core_cost_mult)))return Math.min(r,i);n=i+1}}return 0}function A(e,t){if(null==t)throw new Error("getMaxNumberCacheUpgrades() called without maxLevel arg");if(!p.Player.canAfford(e.calculateCacheUpgradeCost(1)))return 0;let n=1,a=t-1;const r=t-e.cache;if(p.Player.canAfford(e.calculateCacheUpgradeCost(r)))return r;for(;n<=a;){let i=(n+a)/2|0;if(i!=t&&p.Player.canAfford(e.calculateCacheUpgradeCost(i))&&!p.Player.canAfford(e.calculateCacheUpgradeCost(i+1)))return Math.min(r,i);if(p.Player.canAfford(e.calculateCacheUpgradeCost(i))){if(!p.Player.canAfford(e.calculateCacheUpgradeCost(i)))return Math.min(r,i);n=i+1}else a=i-1}return 0}function R(e,t=1){const n=Math.round(t),a=e.calculateLevelUpgradeCost(n,p.Player.hacknet_node_level_cost_mult);if(isNaN(a)||a<=0||n<0)return!1;const r=e instanceof s.HacknetServer;if(e.level>=(r?o.HacknetServerConstants.MaxLevel:o.HacknetNodeConstants.MaxLevel))return!1;if(e.level+n>(r?o.HacknetServerConstants.MaxLevel:o.HacknetNodeConstants.MaxLevel)){return R(e,Math.max(0,(r?o.HacknetServerConstants.MaxLevel:o.HacknetNodeConstants.MaxLevel)-e.level))}return!!p.Player.canAfford(a)&&(p.Player.loseMoney(a),e.upgradeLevel(n,p.Player.hacknet_node_money_mult),!0)}function N(e,t=1){const n=Math.round(t),a=e.calculateRamUpgradeCost(n,p.Player.hacknet_node_ram_cost_mult);if(isNaN(a)||a<=0||n<0)return!1;const r=e instanceof s.HacknetServer;if(e.ram>=(r?o.HacknetServerConstants.MaxRam:o.HacknetNodeConstants.MaxRam))return!1;if(r){if(e.maxRam*Math.pow(2,n)>o.HacknetServerConstants.MaxRam){return N(e,Math.max(0,Math.log2(Math.round(o.HacknetServerConstants.MaxRam/e.maxRam))))}}else if(e.ram*Math.pow(2,n)>o.HacknetNodeConstants.MaxRam){return N(e,Math.max(0,Math.log2(Math.round(o.HacknetNodeConstants.MaxRam/e.ram))))}return!!p.Player.canAfford(a)&&(p.Player.loseMoney(a),e.upgradeRam(n,p.Player.hacknet_node_money_mult),!0)}function D(e,t=1){const n=Math.round(t),a=e.calculateCoreUpgradeCost(n,p.Player.hacknet_node_core_cost_mult);if(isNaN(a)||a<=0||n<0)return!1;const r=e instanceof s.HacknetServer;if(e.cores>=(r?o.HacknetServerConstants.MaxCores:o.HacknetNodeConstants.MaxCores))return!1;if(e.cores+n>(r?o.HacknetServerConstants.MaxCores:o.HacknetNodeConstants.MaxCores)){return D(e,Math.max(0,(r?o.HacknetServerConstants.MaxCores:o.HacknetNodeConstants.MaxCores)-e.cores))}return!!p.Player.canAfford(a)&&(p.Player.loseMoney(a),e.upgradeCore(n,p.Player.hacknet_node_money_mult),!0)}function B(e,t=1){const n=Math.round(t),a=e.calculateCacheUpgradeCost(n);if(isNaN(a)||a<=0||n<0)return!1;if(!(e instanceof s.HacknetServer))return console.warn("purchaseCacheUpgrade() called for a non-HacknetNode"),!1;if(e.cache+n>o.HacknetServerConstants.MaxCache){return B(e,Math.max(0,o.HacknetServerConstants.MaxCache-e.cache))}return!!p.Player.canAfford(a)&&(p.Player.loseMoney(a),e.upgradeCache(n),!0)}function I(){g.routing.isOn(g.Page.HacknetNodes)&&E.a.render(y.a.createElement(v.a,null),k)}function L(){k instanceof HTMLElement&&E.a.unmountComponentAtNode(k),k.style.display="none"}function j(e){return 0===p.Player.hacknetNodes.length?0:C()?function(e){if(!(p.Player.hashManager instanceof l.HashManager))throw new Error("Player does not have a HashManager (should be in 'hashManager' prop)");let t=0;for(let n=0;n{!function(e){null!=t.Companies[e.name]&&console.warn(`Duplicate Company Position being defined: ${e.name}`),t.Companies[e.name]=new r.Company(e)}(e)});for(const n in t.Companies){const a=t.Companies[n];e[n]instanceof r.Company?(a.favor=e[n].favor,isNaN(a.favor)&&(a.favor=0)):a.favor=0}},t.loadCompanies=function(e){t.Companies=JSON.parse(e,i.Reviver)},t.companyExists=function(e){return t.Companies.hasOwnProperty(e)}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.resetIndustryResearchTrees=t.IndustryResearchTrees=t.IndustryDescriptions=t.IndustryStartingCosts=t.Industries=void 0;const a=n(1225),r=n(3);t.Industries={Energy:"Energy",Utilities:"Water Utilities",Agriculture:"Agriculture",Fishing:"Fishing",Mining:"Mining",Food:"Food",Tobacco:"Tobacco",Chemical:"Chemical",Pharmaceutical:"Pharmaceutical",Computer:"Computer Hardware",Robotics:"Robotics",Software:"Software",Healthcare:"Healthcare",RealEstate:"RealEstate"},t.IndustryStartingCosts={Energy:225e9,Utilities:15e10,Agriculture:4e10,Fishing:8e10,Mining:3e11,Food:1e10,Tobacco:2e10,Chemical:7e10,Pharmaceutical:2e11,Computer:5e11,Robotics:1e12,Software:25e9,Healthcare:75e10,RealEstate:6e11},t.IndustryDescriptions={Energy:"Engage in the production and distribution of energy.

        Starting cost: "+r.numeralWrapper.format(t.IndustryStartingCosts.Energy,"$0.000a")+"
        Recommended starting Industry: NO",Utilities:"Distribute water and provide wastewater services.

        Starting cost: "+r.numeralWrapper.format(t.IndustryStartingCosts.Utilities,"$0.000a")+"
        Recommended starting Industry: NO",Agriculture:"Cultivate crops and breed livestock to produce food.

        Starting cost: "+r.numeralWrapper.format(t.IndustryStartingCosts.Agriculture,"$0.000a")+"
        Recommended starting Industry: YES",Fishing:"Produce food through the breeding and processing of fish and fish products.

        Starting cost: "+r.numeralWrapper.format(t.IndustryStartingCosts.Fishing,"$0.000a")+"
        Recommended starting Industry: NO",Mining:"Extract and process metals from the earth.

        Starting cost: "+r.numeralWrapper.format(t.IndustryStartingCosts.Mining,"$0.000a")+"
        Recommended starting Industry: NO",Food:"Create your own restaurants all around the world.

        Starting cost: "+r.numeralWrapper.format(t.IndustryStartingCosts.Food,"$0.000a")+"
        Recommended starting Industry: YES",Tobacco:"Create and distribute tobacco and tobacco-related products.

        Starting cost: "+r.numeralWrapper.format(t.IndustryStartingCosts.Tobacco,"$0.000a")+"
        Recommended starting Industry: YES",Chemical:"Produce industrial chemicals.

        Starting cost: "+r.numeralWrapper.format(t.IndustryStartingCosts.Chemical,"$0.000a")+"
        Recommended starting Industry: NO",Pharmaceutical:"Discover, develop, and create new pharmaceutical drugs.

        Starting cost: "+r.numeralWrapper.format(t.IndustryStartingCosts.Pharmaceutical,"$0.000a")+"
        Recommended starting Industry: NO",Computer:"Develop and manufacture new computer hardware and networking infrastructures.

        Starting cost: "+r.numeralWrapper.format(t.IndustryStartingCosts.Computer,"$0.000a")+"
        Recommended starting Industry: NO",Robotics:"Develop and create robots.

        Starting cost: "+r.numeralWrapper.format(t.IndustryStartingCosts.Robotics,"$0.000a")+"
        Recommended starting Industry: NO",Software:"Develop computer software and create AI Cores.

        Starting cost: "+r.numeralWrapper.format(t.IndustryStartingCosts.Software,"$0.000a")+"
        Recommended starting Industry: YES",Healthcare:"Create and manage hospitals.

        Starting cost: "+r.numeralWrapper.format(t.IndustryStartingCosts.Healthcare,"$0.000a")+"
        Recommended starting Industry: NO",RealEstate:"Develop and manage real estate properties.

        Starting cost: "+r.numeralWrapper.format(t.IndustryStartingCosts.RealEstate,"$0.000a")+"
        Recommended starting Industry: NO"},t.IndustryResearchTrees={Energy:a.getBaseResearchTreeCopy(),Utilities:a.getBaseResearchTreeCopy(),Agriculture:a.getBaseResearchTreeCopy(),Fishing:a.getBaseResearchTreeCopy(),Mining:a.getBaseResearchTreeCopy(),Food:a.getProductIndustryResearchTreeCopy(),Tobacco:a.getProductIndustryResearchTreeCopy(),Chemical:a.getBaseResearchTreeCopy(),Pharmaceutical:a.getProductIndustryResearchTreeCopy(),Computer:a.getProductIndustryResearchTreeCopy(),Robotics:a.getProductIndustryResearchTreeCopy(),Software:a.getProductIndustryResearchTreeCopy(),Healthcare:a.getProductIndustryResearchTreeCopy(),RealEstate:a.getProductIndustryResearchTreeCopy()},t.resetIndustryResearchTrees=function(){t.IndustryResearchTrees.Energy=a.getBaseResearchTreeCopy(),t.IndustryResearchTrees.Utilities=a.getBaseResearchTreeCopy(),t.IndustryResearchTrees.Agriculture=a.getBaseResearchTreeCopy(),t.IndustryResearchTrees.Fishing=a.getBaseResearchTreeCopy(),t.IndustryResearchTrees.Mining=a.getBaseResearchTreeCopy(),t.IndustryResearchTrees.Food=a.getBaseResearchTreeCopy(),t.IndustryResearchTrees.Tobacco=a.getBaseResearchTreeCopy(),t.IndustryResearchTrees.Chemical=a.getBaseResearchTreeCopy(),t.IndustryResearchTrees.Pharmaceutical=a.getBaseResearchTreeCopy(),t.IndustryResearchTrees.Computer=a.getBaseResearchTreeCopy(),t.IndustryResearchTrees.Robotics=a.getBaseResearchTreeCopy(),t.IndustryResearchTrees.Software=a.getBaseResearchTreeCopy(),t.IndustryResearchTrees.Healthcare=a.getBaseResearchTreeCopy(),t.IndustryResearchTrees.RealEstate=a.getBaseResearchTreeCopy()}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.initializeMainMenuLinks=t.MainMenuLinks=void 0;const a=n(53),r=(()=>{const e=document.createElement("div");if(null===e)throw new Error("unable to create empty div element");return e})();t.MainMenuLinks={Terminal:r,ScriptEditor:r,ActiveScripts:r,CreateProgram:r,Stats:r,Factions:r,Augmentations:r,HacknetNodes:r,Sleeves:r,City:r,Travel:r,Job:r,StockMarket:r,Bladeburner:r,Corporation:r,Gang:r,Milestones:r,Tutorial:r,Options:r,DevMenu:r},t.initializeMainMenuLinks=function(){try{function e(e){const t=a.clearEventListeners(e);if(null==t)throw new Error(`clearEventListeners() failed for element with id: ${e}`);return t}t.MainMenuLinks.Terminal=e("terminal-menu-link"),t.MainMenuLinks.ScriptEditor=e("create-script-menu-link"),t.MainMenuLinks.ActiveScripts=e("active-scripts-menu-link"),t.MainMenuLinks.CreateProgram=e("create-program-menu-link"),t.MainMenuLinks.Stats=e("stats-menu-link"),t.MainMenuLinks.Factions=e("factions-menu-link"),t.MainMenuLinks.Augmentations=e("augmentations-menu-link"),t.MainMenuLinks.HacknetNodes=e("hacknet-nodes-menu-link"),t.MainMenuLinks.Sleeves=e("sleeves-menu-link"),t.MainMenuLinks.City=e("city-menu-link"),t.MainMenuLinks.Travel=e("travel-menu-link"),t.MainMenuLinks.Job=e("job-menu-link"),t.MainMenuLinks.StockMarket=e("stock-market-menu-link"),t.MainMenuLinks.Bladeburner=e("bladeburner-menu-link"),t.MainMenuLinks.Corporation=e("corporation-menu-link"),t.MainMenuLinks.Gang=e("gang-menu-link"),t.MainMenuLinks.Milestones=e("milestones-menu-link"),t.MainMenuLinks.Tutorial=e("tutorial-menu-link");const n=document.getElementById("options-menu-link");if(null===n)throw new Error('Could not find element with id: "options-menu-link"');return t.MainMenuLinks.Options=n,t.MainMenuLinks.DevMenu=e("dev-menu-link"),!0}catch(e){return console.error(`Failed to initialize Main Menu Links: ${e}`),!1}}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.HacknetServerConstants=t.HacknetNodeConstants=void 0,t.HacknetNodeConstants={MoneyGainPerLevel:1.6,BaseCost:1e3,LevelBaseCost:1,RamBaseCost:3e4,CoreBaseCost:5e5,PurchaseNextMult:1.85,UpgradeLevelMult:1.04,UpgradeRamMult:1.28,UpgradeCoreMult:1.48,MaxLevel:200,MaxRam:64,MaxCores:16},t.HacknetServerConstants={HashesPerLevel:.001,BaseCost:5e4,RamBaseCost:2e5,CoreBaseCost:1e6,CacheBaseCost:1e7,PurchaseMult:3.2,UpgradeLevelMult:1.1,UpgradeRamMult:1.4,UpgradeCoreMult:1.55,UpgradeCacheMult:1.85,MaxServers:20,MaxLevel:300,MaxRam:8192,MaxCores:128,MaxCache:15}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.CityName=void 0,function(e){e.Aevum="Aevum",e.Chongqing="Chongqing",e.Ishima="Ishima",e.NewTokyo="New Tokyo",e.Sector12="Sector-12",e.Volhaven="Volhaven"}(t.CityName||(t.CityName={}))},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.KEY=void 0,t.KEY={CTRL:17,DOWNARROW:40,ENTER:13,ESC:27,TAB:9,UPARROW:38,0:48,1:49,2:50,3:51,4:52,5:53,6:54,7:55,8:56,9:57,A:65,B:66,C:67,D:68,E:69,F:70,G:71,H:72,I:73,J:74,K:75,L:76,M:77,N:78,O:79,P:80,Q:81,R:82,S:83,T:84,U:85,V:86,W:87,X:88,Y:89,Z:90}},,,function(module,__webpack_exports__,__webpack_require__){"use strict";(function($){__webpack_require__.d(__webpack_exports__,"f",function(){return IssueNewSharesCooldown}),__webpack_require__.d(__webpack_exports__,"k",function(){return SellSharesCooldown}),__webpack_require__.d(__webpack_exports__,"m",function(){return WarehouseInitialCost}),__webpack_require__.d(__webpack_exports__,"n",function(){return WarehouseInitialSize}),__webpack_require__.d(__webpack_exports__,"o",function(){return WarehouseUpgradeBaseCost}),__webpack_require__.d(__webpack_exports__,"g",function(){return OfficeInitialCost}),__webpack_require__.d(__webpack_exports__,"h",function(){return OfficeInitialSize}),__webpack_require__.d(__webpack_exports__,"a",function(){return BribeThreshold}),__webpack_require__.d(__webpack_exports__,"b",function(){return BribeToRepRatio}),__webpack_require__.d(__webpack_exports__,"j",function(){return ProductProductionCostRatio}),__webpack_require__.d(__webpack_exports__,"d",function(){return DividendMaxPercentage}),__webpack_require__.d(__webpack_exports__,"c",function(){return Corporation}),__webpack_require__.d(__webpack_exports__,"e",function(){return Industry}),__webpack_require__.d(__webpack_exports__,"i",function(){return OfficeSpace});var _CorporationState__WEBPACK_IMPORTED_MODULE_0__=__webpack_require__(541),_CorporationState__WEBPACK_IMPORTED_MODULE_0___default=__webpack_require__.n(_CorporationState__WEBPACK_IMPORTED_MODULE_0__),_data_CorporationUnlockUpgrades__WEBPACK_IMPORTED_MODULE_1__=__webpack_require__(458),_data_CorporationUnlockUpgrades__WEBPACK_IMPORTED_MODULE_1___default=__webpack_require__.n(_data_CorporationUnlockUpgrades__WEBPACK_IMPORTED_MODULE_1__),_data_CorporationUpgrades__WEBPACK_IMPORTED_MODULE_2__=__webpack_require__(457),_data_CorporationUpgrades__WEBPACK_IMPORTED_MODULE_2___default=__webpack_require__.n(_data_CorporationUpgrades__WEBPACK_IMPORTED_MODULE_2__),_EmployeePositions__WEBPACK_IMPORTED_MODULE_3__=__webpack_require__(28),_EmployeePositions__WEBPACK_IMPORTED_MODULE_3___default=__webpack_require__.n(_EmployeePositions__WEBPACK_IMPORTED_MODULE_3__),_IndustryData__WEBPACK_IMPORTED_MODULE_4__=__webpack_require__(36),_IndustryData__WEBPACK_IMPORTED_MODULE_4___default=__webpack_require__.n(_IndustryData__WEBPACK_IMPORTED_MODULE_4__),_IndustryUpgrades__WEBPACK_IMPORTED_MODULE_5__=__webpack_require__(314),_IndustryUpgrades__WEBPACK_IMPORTED_MODULE_5___default=__webpack_require__.n(_IndustryUpgrades__WEBPACK_IMPORTED_MODULE_5__),_Material__WEBPACK_IMPORTED_MODULE_6__=__webpack_require__(299),_Material__WEBPACK_IMPORTED_MODULE_6___default=__webpack_require__.n(_Material__WEBPACK_IMPORTED_MODULE_6__),_MaterialSizes__WEBPACK_IMPORTED_MODULE_7__=__webpack_require__(141),_MaterialSizes__WEBPACK_IMPORTED_MODULE_7___default=__webpack_require__.n(_MaterialSizes__WEBPACK_IMPORTED_MODULE_7__),_Product__WEBPACK_IMPORTED_MODULE_8__=__webpack_require__(212),_Product__WEBPACK_IMPORTED_MODULE_8___default=__webpack_require__.n(_Product__WEBPACK_IMPORTED_MODULE_8__),_ResearchMap__WEBPACK_IMPORTED_MODULE_9__=__webpack_require__(418),_ResearchMap__WEBPACK_IMPORTED_MODULE_9___default=__webpack_require__.n(_ResearchMap__WEBPACK_IMPORTED_MODULE_9__),_Warehouse__WEBPACK_IMPORTED_MODULE_10__=__webpack_require__(125),_Warehouse__WEBPACK_IMPORTED_MODULE_10___default=__webpack_require__.n(_Warehouse__WEBPACK_IMPORTED_MODULE_10__);__webpack_require__.d(__webpack_exports__,"l",function(){return _Warehouse__WEBPACK_IMPORTED_MODULE_10__.Warehouse});var _BitNode_BitNodeMultipliers__WEBPACK_IMPORTED_MODULE_11__=__webpack_require__(24),_BitNode_BitNodeMultipliers__WEBPACK_IMPORTED_MODULE_11___default=__webpack_require__.n(_BitNode_BitNodeMultipliers__WEBPACK_IMPORTED_MODULE_11__),_Literature_LiteratureHelpers__WEBPACK_IMPORTED_MODULE_12__=__webpack_require__(456),_Literature_LiteratureHelpers__WEBPACK_IMPORTED_MODULE_12___default=__webpack_require__.n(_Literature_LiteratureHelpers__WEBPACK_IMPORTED_MODULE_12__),_Literature_data_LiteratureNames__WEBPACK_IMPORTED_MODULE_13__=__webpack_require__(148),_Literature_data_LiteratureNames__WEBPACK_IMPORTED_MODULE_13___default=__webpack_require__.n(_Literature_data_LiteratureNames__WEBPACK_IMPORTED_MODULE_13__),_Locations_data_CityNames__WEBPACK_IMPORTED_MODULE_14__=__webpack_require__(39),_Locations_data_CityNames__WEBPACK_IMPORTED_MODULE_14___default=__webpack_require__.n(_Locations_data_CityNames__WEBPACK_IMPORTED_MODULE_14__),_Player__WEBPACK_IMPORTED_MODULE_15__=__webpack_require__(1),_ui_numeralFormat__WEBPACK_IMPORTED_MODULE_16__=__webpack_require__(3),_ui_numeralFormat__WEBPACK_IMPORTED_MODULE_16___default=__webpack_require__.n(_ui_numeralFormat__WEBPACK_IMPORTED_MODULE_16__),_ui_navigationTracking__WEBPACK_IMPORTED_MODULE_17__=__webpack_require__(16),_ui_navigationTracking__WEBPACK_IMPORTED_MODULE_17___default=__webpack_require__.n(_ui_navigationTracking__WEBPACK_IMPORTED_MODULE_17__),_utils_calculateEffectWithFactors__WEBPACK_IMPORTED_MODULE_18__=__webpack_require__(790),_utils_calculateEffectWithFactors__WEBPACK_IMPORTED_MODULE_18___default=__webpack_require__.n(_utils_calculateEffectWithFactors__WEBPACK_IMPORTED_MODULE_18__),_utils_DialogBox__WEBPACK_IMPORTED_MODULE_19__=__webpack_require__(12),_utils_JSONReviver__WEBPACK_IMPORTED_MODULE_20__=__webpack_require__(26),_utils_uiHelpers_appendLineBreaks__WEBPACK_IMPORTED_MODULE_21__=__webpack_require__(108),_utils_uiHelpers_appendLineBreaks__WEBPACK_IMPORTED_MODULE_21___default=__webpack_require__.n(_utils_uiHelpers_appendLineBreaks__WEBPACK_IMPORTED_MODULE_21__),_utils_uiHelpers_createElement__WEBPACK_IMPORTED_MODULE_22__=__webpack_require__(7),_utils_uiHelpers_createElement__WEBPACK_IMPORTED_MODULE_22___default=__webpack_require__.n(_utils_uiHelpers_createElement__WEBPACK_IMPORTED_MODULE_22__),_utils_uiHelpers_createPopup__WEBPACK_IMPORTED_MODULE_23__=__webpack_require__(54),_utils_uiHelpers_createPopup__WEBPACK_IMPORTED_MODULE_23___default=__webpack_require__.n(_utils_uiHelpers_createPopup__WEBPACK_IMPORTED_MODULE_23__),_utils_uiHelpers_createPopupCloseButton__WEBPACK_IMPORTED_MODULE_24__=__webpack_require__(73),_utils_uiHelpers_createPopupCloseButton__WEBPACK_IMPORTED_MODULE_24___default=__webpack_require__.n(_utils_uiHelpers_createPopupCloseButton__WEBPACK_IMPORTED_MODULE_24__),_utils_StringHelperFunctions__WEBPACK_IMPORTED_MODULE_25__=__webpack_require__(14),_utils_StringHelperFunctions__WEBPACK_IMPORTED_MODULE_25___default=__webpack_require__.n(_utils_StringHelperFunctions__WEBPACK_IMPORTED_MODULE_25__),_utils_helpers_getRandomInt__WEBPACK_IMPORTED_MODULE_26__=__webpack_require__(22),_utils_helpers_getRandomInt__WEBPACK_IMPORTED_MODULE_26___default=__webpack_require__.n(_utils_helpers_getRandomInt__WEBPACK_IMPORTED_MODULE_26__),_utils_helpers_isString__WEBPACK_IMPORTED_MODULE_27__=__webpack_require__(65),_utils_helpers_isString__WEBPACK_IMPORTED_MODULE_27___default=__webpack_require__.n(_utils_helpers_isString__WEBPACK_IMPORTED_MODULE_27__),_utils_helpers_keyCodes__WEBPACK_IMPORTED_MODULE_28__=__webpack_require__(40),_utils_helpers_keyCodes__WEBPACK_IMPORTED_MODULE_28___default=__webpack_require__.n(_utils_helpers_keyCodes__WEBPACK_IMPORTED_MODULE_28__),_utils_uiHelpers_removeElement__WEBPACK_IMPORTED_MODULE_29__=__webpack_require__(146),_utils_uiHelpers_removeElement__WEBPACK_IMPORTED_MODULE_29___default=__webpack_require__.n(_utils_uiHelpers_removeElement__WEBPACK_IMPORTED_MODULE_29__),_utils_uiHelpers_removeElementById__WEBPACK_IMPORTED_MODULE_30__=__webpack_require__(47),_utils_uiHelpers_removeElementById__WEBPACK_IMPORTED_MODULE_30___default=__webpack_require__.n(_utils_uiHelpers_removeElementById__WEBPACK_IMPORTED_MODULE_30__),_utils_YesNoBox__WEBPACK_IMPORTED_MODULE_31__=__webpack_require__(58),_utils_YesNoBox__WEBPACK_IMPORTED_MODULE_31___default=__webpack_require__.n(_utils_YesNoBox__WEBPACK_IMPORTED_MODULE_31__),react__WEBPACK_IMPORTED_MODULE_32__=__webpack_require__(0),react__WEBPACK_IMPORTED_MODULE_32___default=__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_32__),react_dom__WEBPACK_IMPORTED_MODULE_33__=__webpack_require__(25),react_dom__WEBPACK_IMPORTED_MODULE_33___default=__webpack_require__.n(react_dom__WEBPACK_IMPORTED_MODULE_33__),_ui_CorporationUIEventHandler__WEBPACK_IMPORTED_MODULE_34__=__webpack_require__(789),_ui_Root__WEBPACK_IMPORTED_MODULE_35__=__webpack_require__(787),_ui_Routing__WEBPACK_IMPORTED_MODULE_36__=__webpack_require__(455),_ui_Routing__WEBPACK_IMPORTED_MODULE_36___default=__webpack_require__.n(_ui_Routing__WEBPACK_IMPORTED_MODULE_36__),decimal_js__WEBPACK_IMPORTED_MODULE_37__=__webpack_require__(59);const INITIALSHARES=1e9,SHARESPERPRICEUPDATE=1e6,IssueNewSharesCooldown=216e3,SellSharesCooldown=18e3,CyclesPerMarketCycle=50,CyclesPerIndustryStateCycle=CyclesPerMarketCycle/_CorporationState__WEBPACK_IMPORTED_MODULE_0__.AllCorporationStates.length,SecsPerMarketCycle=CyclesPerMarketCycle/5,Cities=["Aevum","Chongqing","Sector-12","New Tokyo","Ishima","Volhaven"],WarehouseInitialCost=5e9,WarehouseInitialSize=100,WarehouseUpgradeBaseCost=1e9,OfficeInitialCost=4e9,OfficeInitialSize=3,OfficeUpgradeBaseCost=1e9,BribeThreshold=1e14,BribeToRepRatio=1e9,ProductProductionCostRatio=5,DividendMaxPercentage=50,EmployeeSalaryMultiplier=3,CyclesPerEmployeeRaise=400,EmployeeRaiseAmount=50,BaseMaxProducts=3;let researchTreeBoxOpened=!1,researchTreeBox=null;function Industry(e={}){this.offices={[_Locations_data_CityNames__WEBPACK_IMPORTED_MODULE_14__.CityName.Aevum]:0,[_Locations_data_CityNames__WEBPACK_IMPORTED_MODULE_14__.CityName.Chongqing]:0,[_Locations_data_CityNames__WEBPACK_IMPORTED_MODULE_14__.CityName.Sector12]:new OfficeSpace({loc:_Locations_data_CityNames__WEBPACK_IMPORTED_MODULE_14__.CityName.Sector12,size:OfficeInitialSize}),[_Locations_data_CityNames__WEBPACK_IMPORTED_MODULE_14__.CityName.NewTokyo]:0,[_Locations_data_CityNames__WEBPACK_IMPORTED_MODULE_14__.CityName.Ishima]:0,[_Locations_data_CityNames__WEBPACK_IMPORTED_MODULE_14__.CityName.Volhaven]:0},this.name=e.name?e.name:0,this.type=e.type?e.type:0,this.sciResearch=new _Material__WEBPACK_IMPORTED_MODULE_6__.Material({name:"Scientific Research"}),this.researched={},this.reqMats={},this.prodMats=[],this.products={},this.makesProducts=!1,this.awareness=0,this.popularity=0,this.startingCost=0,this.reFac=0,this.sciFac=0,this.hwFac=0,this.robFac=0,this.aiFac=0,this.advFac=0,this.prodMult=0,this.lastCycleRevenue=new decimal_js__WEBPACK_IMPORTED_MODULE_37__.a(0),this.lastCycleExpenses=new decimal_js__WEBPACK_IMPORTED_MODULE_37__.a(0),this.thisCycleRevenue=new decimal_js__WEBPACK_IMPORTED_MODULE_37__.a(0),this.thisCycleExpenses=new decimal_js__WEBPACK_IMPORTED_MODULE_37__.a(0);var t=Object.keys(_IndustryUpgrades__WEBPACK_IMPORTED_MODULE_5__.IndustryUpgrades).length;this.upgrades=Array(t).fill(0),this.state="START",this.newInd=!0,this.warehouses={[_Locations_data_CityNames__WEBPACK_IMPORTED_MODULE_14__.CityName.Aevum]:0,[_Locations_data_CityNames__WEBPACK_IMPORTED_MODULE_14__.CityName.Chonqing]:0,[_Locations_data_CityNames__WEBPACK_IMPORTED_MODULE_14__.CityName.Sector12]:new _Warehouse__WEBPACK_IMPORTED_MODULE_10__.Warehouse({corp:e.corp,industry:this,loc:_Locations_data_CityNames__WEBPACK_IMPORTED_MODULE_14__.CityName.Sector12,size:WarehouseInitialSize}),[_Locations_data_CityNames__WEBPACK_IMPORTED_MODULE_14__.CityName.NewTokyo]:0,[_Locations_data_CityNames__WEBPACK_IMPORTED_MODULE_14__.CityName.Ishima]:0,[_Locations_data_CityNames__WEBPACK_IMPORTED_MODULE_14__.CityName.Volhaven]:0},this.init()}function Employee(e={}){if(!(this instanceof Employee))return new Employee(e);this.name=e.name?e.name:"Bobby",this.mor=e.morale?e.morale:Object(_utils_helpers_getRandomInt__WEBPACK_IMPORTED_MODULE_26__.getRandomInt)(50,100),this.hap=e.happiness?e.happiness:Object(_utils_helpers_getRandomInt__WEBPACK_IMPORTED_MODULE_26__.getRandomInt)(50,100),this.ene=e.energy?e.energy:Object(_utils_helpers_getRandomInt__WEBPACK_IMPORTED_MODULE_26__.getRandomInt)(50,100),this.int=e.intelligence?e.intelligence:Object(_utils_helpers_getRandomInt__WEBPACK_IMPORTED_MODULE_26__.getRandomInt)(10,50),this.cha=e.charisma?e.charisma:Object(_utils_helpers_getRandomInt__WEBPACK_IMPORTED_MODULE_26__.getRandomInt)(10,50),this.exp=e.experience?e.experience:Object(_utils_helpers_getRandomInt__WEBPACK_IMPORTED_MODULE_26__.getRandomInt)(10,50),this.cre=e.creativity?e.creativity:Object(_utils_helpers_getRandomInt__WEBPACK_IMPORTED_MODULE_26__.getRandomInt)(10,50),this.eff=e.efficiency?e.efficiency:Object(_utils_helpers_getRandomInt__WEBPACK_IMPORTED_MODULE_26__.getRandomInt)(10,50),this.sal=e.salary?e.salary:Object(_utils_helpers_getRandomInt__WEBPACK_IMPORTED_MODULE_26__.getRandomInt)(.1,5),this.pro=0,this.cyclesUntilRaise=CyclesPerEmployeeRaise,this.loc=e.loc?e.loc:"",this.pos=_EmployeePositions__WEBPACK_IMPORTED_MODULE_3__.EmployeePositions.Unassigned}$(document).mousedown(function(e){researchTreeBoxOpened&&null==$(e.target).closest("#corporation-research-popup-box-content").get(0)&&(Object(_utils_uiHelpers_removeElement__WEBPACK_IMPORTED_MODULE_29__.removeElement)(researchTreeBox),researchTreeBox=null,researchTreeBoxOpened=!1)}),Industry.prototype.init=function(){switch(this.startingCost=_IndustryData__WEBPACK_IMPORTED_MODULE_4__.IndustryStartingCosts[this.type],this.type){case _IndustryData__WEBPACK_IMPORTED_MODULE_4__.Industries.Energy:this.reFac=.65,this.sciFac=.7,this.robFac=.05,this.aiFac=.3,this.advFac=.08,this.reqMats={Hardware:.1,Metal:.2},this.prodMats=["Energy"];break;case _IndustryData__WEBPACK_IMPORTED_MODULE_4__.Industries.Utilities:case"Utilities":this.reFac=.5,this.sciFac=.6,this.robFac=.4,this.aiFac=.4,this.advFac=.08,this.reqMats={Hardware:.1,Metal:.1},this.prodMats=["Water"];break;case _IndustryData__WEBPACK_IMPORTED_MODULE_4__.Industries.Agriculture:this.reFac=.72,this.sciFac=.5,this.hwFac=.2,this.robFac=.3,this.aiFac=.3,this.advFac=.04,this.reqMats={Water:.5,Energy:.5},this.prodMats=["Plants","Food"];break;case _IndustryData__WEBPACK_IMPORTED_MODULE_4__.Industries.Fishing:this.reFac=.15,this.sciFac=.35,this.hwFac=.35,this.robFac=.5,this.aiFac=.2,this.advFac=.08,this.reqMats={Energy:.5},this.prodMats=["Food"];break;case _IndustryData__WEBPACK_IMPORTED_MODULE_4__.Industries.Mining:this.reFac=.3,this.sciFac=.26,this.hwFac=.4,this.robFac=.45,this.aiFac=.45,this.advFac=.06,this.reqMats={Energy:.8},this.prodMats=["Metal"];break;case _IndustryData__WEBPACK_IMPORTED_MODULE_4__.Industries.Food:this.sciFac=.12,this.hwFac=.15,this.robFac=.3,this.aiFac=.25,this.advFac=.25,this.reFac=.05,this.reqMats={Food:.5,Water:.5,Energy:.2},this.makesProducts=!0;break;case _IndustryData__WEBPACK_IMPORTED_MODULE_4__.Industries.Tobacco:this.reFac=.15,this.sciFac=.75,this.hwFac=.15,this.robFac=.2,this.aiFac=.15,this.advFac=.2,this.reqMats={Plants:1,Water:.2},this.makesProducts=!0;break;case _IndustryData__WEBPACK_IMPORTED_MODULE_4__.Industries.Chemical:this.reFac=.25,this.sciFac=.75,this.hwFac=.2,this.robFac=.25,this.aiFac=.2,this.advFac=.07,this.reqMats={Plants:1,Energy:.5,Water:.5},this.prodMats=["Chemicals"];break;case _IndustryData__WEBPACK_IMPORTED_MODULE_4__.Industries.Pharmaceutical:this.reFac=.05,this.sciFac=.8,this.hwFac=.15,this.robFac=.25,this.aiFac=.2,this.advFac=.16,this.reqMats={Chemicals:2,Energy:1,Water:.5},this.prodMats=["Drugs"],this.makesProducts=!0;break;case _IndustryData__WEBPACK_IMPORTED_MODULE_4__.Industries.Computer:case"Computer":this.reFac=.2,this.sciFac=.62,this.robFac=.36,this.aiFac=.19,this.advFac=.17,this.reqMats={Metal:2,Energy:1},this.prodMats=["Hardware"],this.makesProducts=!0;break;case _IndustryData__WEBPACK_IMPORTED_MODULE_4__.Industries.Robotics:this.reFac=.32,this.sciFac=.65,this.aiFac=.36,this.advFac=.18,this.hwFac=.19,this.reqMats={Hardware:5,Energy:3},this.prodMats=["Robots"],this.makesProducts=!0;break;case _IndustryData__WEBPACK_IMPORTED_MODULE_4__.Industries.Software:this.sciFac=.62,this.advFac=.16,this.hwFac=.25,this.reFac=.15,this.aiFac=.18,this.robFac=.05,this.reqMats={Hardware:.5,Energy:.5},this.prodMats=["AICores"],this.makesProducts=!0;break;case _IndustryData__WEBPACK_IMPORTED_MODULE_4__.Industries.Healthcare:this.reFac=.1,this.sciFac=.75,this.advFac=.11,this.hwFac=.1,this.robFac=.1,this.aiFac=.1,this.reqMats={Robots:10,AICores:5,Energy:5,Water:5},this.makesProducts=!0;break;case _IndustryData__WEBPACK_IMPORTED_MODULE_4__.Industries.RealEstate:this.robFac=.6,this.aiFac=.6,this.advFac=.25,this.sciFac=.05,this.hwFac=.05,this.reqMats={Metal:5,Energy:5,Water:2,Hardware:4},this.prodMats=["RealEstate"],this.makesProducts=!0;break;default:return void console.error(`Invalid Industry Type passed into Industry.init(): ${this.type}`)}},Industry.prototype.getProductDescriptionText=function(){if(this.makesProducts)switch(this.type){case _IndustryData__WEBPACK_IMPORTED_MODULE_4__.Industries.Food:return"create and manage restaurants";case _IndustryData__WEBPACK_IMPORTED_MODULE_4__.Industries.Tobacco:return"create tobacco and tobacco-related products";case _IndustryData__WEBPACK_IMPORTED_MODULE_4__.Industries.Pharmaceutical:return"develop new pharmaceutical drugs";case _IndustryData__WEBPACK_IMPORTED_MODULE_4__.Industries.Computer:case"Computer":return"create new computer hardware and networking infrastructures";case _IndustryData__WEBPACK_IMPORTED_MODULE_4__.Industries.Robotics:return"build specialized robots and robot-related products";case _IndustryData__WEBPACK_IMPORTED_MODULE_4__.Industries.Software:return"develop computer software";case _IndustryData__WEBPACK_IMPORTED_MODULE_4__.Industries.Healthcare:return"build and manage hospitals";case _IndustryData__WEBPACK_IMPORTED_MODULE_4__.Industries.RealEstate:return"develop and manage real estate properties";default:return console.error("Invalid industry type in Industry.getProductDescriptionText"),""}},Industry.prototype.getMaximumNumberProducts=function(){if(!this.makesProducts)return 0;let e=0;return this.hasResearch("uPgrade: Capacity.I")&&++e,this.hasResearch("uPgrade: Capacity.II")&&++e,BaseMaxProducts+e},Industry.prototype.hasMaximumNumberProducts=function(){return Object.keys(this.products).length>=this.getMaximumNumberProducts()},Industry.prototype.calculateProductionFactors=function(){for(var e=0,t=0;t0&&(e.breakdown+=t+": "+Object(_utils_StringHelperFunctions__WEBPACK_IMPORTED_MODULE_25__.formatNumber)(n.data[e.loc][0]*n.siz,0)+"
        ")}},Industry.prototype.process=function(e=1,t,n){if(this.state=t,"START"===t){(isNaN(this.thisCycleRevenue)||isNaN(this.thisCycleExpenses))&&(console.error("NaN in Corporation's computed revenue/expenses"),Object(_utils_DialogBox__WEBPACK_IMPORTED_MODULE_19__.dialogBoxCreate)("Something went wrong when compting Corporation's revenue/expenses. This is a bug. Please report to game developer"),this.thisCycleRevenue=new decimal_js__WEBPACK_IMPORTED_MODULE_37__.a(0),this.thisCycleExpenses=new decimal_js__WEBPACK_IMPORTED_MODULE_37__.a(0)),this.lastCycleRevenue=this.thisCycleRevenue.dividedBy(e*SecsPerMarketCycle),this.lastCycleExpenses=this.thisCycleExpenses.dividedBy(e*SecsPerMarketCycle),this.thisCycleRevenue=new decimal_js__WEBPACK_IMPORTED_MODULE_37__.a(0),this.thisCycleExpenses=new decimal_js__WEBPACK_IMPORTED_MODULE_37__.a(0),this.lastCycleRevenue.gt(0)&&(this.newInd=!1);var a=0;for(var r in this.offices)this.offices[r]instanceof OfficeSpace&&(a+=this.offices[r].process(e,{industry:this,corporation:n}));this.thisCycleExpenses=this.thisCycleExpenses.plus(a),this.processMaterialMarket(e),this.processProductMarket(e),this.popularity-=1e-4*e,this.popularity=Math.max(0,this.popularity);var i=n.getDreamSenseGain(),o=4*i;return void(i>0&&(this.popularity+=i*e,this.awareness+=o*e))}let s=this.processMaterials(e,n);Array.isArray(s)&&(this.thisCycleRevenue=this.thisCycleRevenue.plus(s[0]),this.thisCycleExpenses=this.thisCycleExpenses.plus(s[1])),s=this.processProducts(e,n),Array.isArray(s)&&(this.thisCycleRevenue=this.thisCycleRevenue.plus(s[0]),this.thisCycleExpenses=this.thisCycleExpenses.plus(s[1]))},Industry.prototype.processMaterialMarket=function(){for(var e=this.reqMats,t=this.prodMats,n=0;n0&&(r.qty+=a,expenses+=a*r.bCost)}(matName,this),this.updateWarehouseSizeUsed(warehouse));break;case"PRODUCTION":if(warehouse.smartSupplyStore=0,this.prodMats.length>0){var mat=warehouse.materials[this.prodMats[0]],maxProd=this.getOfficeProductivity(office)*this.prodMult*company.getProductionMultiplier()*this.getProductionMultiplier();let e;e=mat.prdman[0]?Math.min(maxProd,mat.prdman[1]):maxProd,e*=SecsPerMarketCycle*marketCycles;var totalMatSize=0;for(let e=0;e0){var maxAmt=Math.floor((warehouse.size-warehouse.sizeUsed)/totalMatSize);e=Math.min(maxAmt,e)}e<0&&(e=0),warehouse.smartSupplyStore+=e/(SecsPerMarketCycle*marketCycles);var producableFrac=1;for(var reqMatName in this.reqMats)if(this.reqMats.hasOwnProperty(reqMatName)){var req=this.reqMats[reqMatName]*e;warehouse.materials[reqMatName].qty0&&e>0){for(const t in this.reqMats){var reqMatQtyNeeded=this.reqMats[t]*e*producableFrac;warehouse.materials[t].qty-=reqMatQtyNeeded,warehouse.materials[t].prd=0,warehouse.materials[t].prd-=reqMatQtyNeeded/(SecsPerMarketCycle*marketCycles)}for(let t=0;tmat.bCost?sCost-mat.bCost>markupLimit&&(markup=Math.pow(markupLimit/(sCost-mat.bCost),2)):sCost=0?(mat.qty-=sellAmt,revenue+=sellAmt*sCost,mat.sll=sellAmt/(SecsPerMarketCycle*marketCycles)):mat.sll=0}break;case"EXPORT":for(var matName in warehouse.materials)if(warehouse.materials.hasOwnProperty(matName)){var mat=warehouse.materials[matName];mat.totalExp=0;for(var expI=0;expI=expWarehouse.size)return[0,0];var maxAmt=Math.floor((expWarehouse.size-expWarehouse.sizeUsed)/_MaterialSizes__WEBPACK_IMPORTED_MODULE_7__.MaterialSizes[matName]);amt=Math.min(maxAmt,amt),expWarehouse.materials[matName].imp+=amt/(SecsPerMarketCycle*marketCycles),expWarehouse.materials[matName].qty+=amt,expWarehouse.materials[matName].qlt=mat.qlt,mat.qty-=amt,mat.totalExp+=amt,expIndustry.updateWarehouseSizeUsed(expWarehouse);break}}}mat.totalExp/=SecsPerMarketCycle*marketCycles}break;case"START":break;default:console.error(`Invalid state: ${this.state}`)}this.updateWarehouseSizeUsed(warehouse)}office instanceof OfficeSpace&&(this.sciResearch.qty+=.004*Math.pow(office.employeeProd[_EmployeePositions__WEBPACK_IMPORTED_MODULE_3__.EmployeePositions.RandD],.5)*company.getScientificResearchMultiplier()*this.getScientificResearchMultiplier())}return[revenue,expenses]},Industry.prototype.processProducts=function(e=1,t){var n=0;if("PRODUCTION"===this.state)for(const t in this.products){const n=this.products[t];if(!n.fin){const t=n.createCity,a=this.offices[t],r=a.employeeProd[_EmployeePositions__WEBPACK_IMPORTED_MODULE_3__.EmployeePositions.Engineer],i=a.employeeProd[_EmployeePositions__WEBPACK_IMPORTED_MODULE_3__.EmployeePositions.Management],o=a.employeeProd[_EmployeePositions__WEBPACK_IMPORTED_MODULE_3__.EmployeePositions.Operations],s=r+i+o;if(s<=0)break;const l=1+i/(1.2*s),c=(Math.pow(r,.34)+Math.pow(o,.2))*l;n.createProduct(e,c),n.prog>=100&&n.finishProduct(a.employeeProd,this);break}}for(var a in this.products)if(this.products.hasOwnProperty(a)){var r=this.products[a];r instanceof _Product__WEBPACK_IMPORTED_MODULE_8__.Product&&r.fin&&(n+=this.processProduct(e,r,t))}return[n,0]},Industry.prototype.processProduct=function(marketCycles=1,product,corporation){let totalProfit=0;for(let i=0;i0){var maxAmt=Math.floor((warehouse.size-warehouse.sizeUsed)/netStorageSize);e=Math.min(maxAmt,e)}warehouse.smartSupplyStore+=e/(SecsPerMarketCycle*marketCycles);var producableFrac=1;for(var reqMatName in product.reqMats)if(product.reqMats.hasOwnProperty(reqMatName)){var req=product.reqMats[reqMatName]*e;warehouse.materials[reqMatName].qty0&&e>0){for(var reqMatName in product.reqMats)if(product.reqMats.hasOwnProperty(reqMatName)){var reqMatQtyNeeded=product.reqMats[reqMatName]*e*producableFrac;warehouse.materials[reqMatName].qty-=reqMatQtyNeeded,warehouse.materials[reqMatName].prd-=reqMatQtyNeeded/(SecsPerMarketCycle*marketCycles)}product.data[city][0]+=e*producableFrac}product.data[city][1]=e*producableFrac/(SecsPerMarketCycle*marketCycles);break}case"SALE":{for(var reqMatName in product.pCost=0,product.reqMats)product.reqMats.hasOwnProperty(reqMatName)&&(product.pCost+=product.reqMats[reqMatName]*warehouse.materials[reqMatName].bCost);product.pCost*=ProductProductionCostRatio;const businessFactor=this.getBusinessFactor(office),advertisingFactor=this.getAdvertisingFactors()[0],marketFactor=this.getMarketFactor(product),markupLimit=product.rat/product.mku;var sCost;if(product.marketTa2){const e=product.data[city][1],t=markupLimit,n=e,a=.5*Math.pow(product.rat,.65)*marketFactor*corporation.getSalesMultiplier()*businessFactor*advertisingFactor*this.getSalesMultiplier(),r=Math.sqrt(n/a);let i;0===a||0===r?0===n?i=0:(i=product.pCost+markupLimit,console.warn("In Corporation, found illegal 0s when trying to calculate MarketTA2 sale cost")):i=t/r+product.pCost,product.marketTa2Price[city]=i,sCost=i}else product.marketTa1?sCost=product.pCost+markupLimit:Object(_utils_helpers_isString__WEBPACK_IMPORTED_MODULE_27__.isString)(product.sCost)?(sCost=product.sCost.replace(/MP/g,product.pCost+product.rat/product.mku),sCost=eval(sCost)):sCost=product.sCost;var markup=1;sCost>product.pCost&&sCost-product.pCost>markupLimit&&(markup=markupLimit/(sCost-product.pCost));var maxSell=.5*Math.pow(product.rat,.65)*marketFactor*corporation.getSalesMultiplier()*Math.pow(markup,2)*businessFactor*advertisingFactor*this.getSalesMultiplier(),sellAmt;if(product.sllman[city][0]&&Object(_utils_helpers_isString__WEBPACK_IMPORTED_MODULE_27__.isString)(product.sllman[city][1])){var tmp=product.sllman[city][1].replace(/MAX/g,maxSell);tmp=tmp.replace(/PROD/g,product.data[city][1]);try{tmp=eval(tmp)}catch(e){Object(_utils_DialogBox__WEBPACK_IMPORTED_MODULE_19__.dialogBoxCreate)("Error evaluating your sell price expression for "+product.name+" in "+this.name+"'s "+city+" office. Sell price is being set to MAX"),tmp=maxSell}sellAmt=Math.min(maxSell,tmp)}else sellAmt=product.sllman[city][0]&&product.sllman[city][1]>0?Math.min(maxSell,product.sllman[city][1]):!1===product.sllman[city][0]?0:maxSell;sellAmt<0&&(sellAmt=0),sellAmt=sellAmt*SecsPerMarketCycle*marketCycles,sellAmt=Math.min(product.data[city][0],sellAmt),sellAmt&&sCost?(product.data[city][0]-=sellAmt,totalProfit+=sellAmt*sCost,product.data[city][2]=sellAmt/(SecsPerMarketCycle*marketCycles)):product.data[city][2]=0;break}case"START":case"PURCHASE":case"EXPORT":break;default:console.error(`Invalid State: ${this.state}`)}}return totalProfit},Industry.prototype.discontinueProduct=function(e){for(var t in this.products)this.products.hasOwnProperty(t)&&e===this.products[t]&&delete this.products[t]},Industry.prototype.upgrade=function(e,t){for(var n=t.corporation,a=t.office,r=e[0];this.upgrades.length<=r;)this.upgrades.push(0);switch(++this.upgrades[r],r){case 0:for(let e=0;e{if(this.sciResearch.qty>=n.cost)return this.sciResearch.qty-=n.cost,t.research(a[e]),this.researched[a[e]]=!0,Object(_utils_DialogBox__WEBPACK_IMPORTED_MODULE_19__.dialogBoxCreate)(`Researched ${a[e]}. It may take a market cycle `+`(~${SecsPerMarketCycle} seconds) before the effects of `+"the Research apply."),this.createResearchBox();Object(_utils_DialogBox__WEBPACK_IMPORTED_MODULE_19__.dialogBoxCreate)(`You do not have enough Scientific Research for ${n.name}`)}):console.warn(`Could not find Research Tree div for ${r}`)}const r=document.getElementById(`${e}-content`);null!=r&&(Object(_utils_uiHelpers_appendLineBreaks__WEBPACK_IMPORTED_MODULE_21__.appendLineBreaks)(r,2),r.appendChild(Object(_utils_uiHelpers_createElement__WEBPACK_IMPORTED_MODULE_22__.createElement)("pre",{display:"block",innerText:"Multipliers from research:\n"+` * Advertising Multiplier: x${t.getAdvertisingMultiplier()}\n`+` * Employee Charisma Multiplier: x${t.getEmployeeChaMultiplier()}\n`+` * Employee Creativity Multiplier: x${t.getEmployeeCreMultiplier()}\n`+` * Employee Efficiency Multiplier: x${t.getEmployeeEffMultiplier()}\n`+` * Employee Intelligence Multiplier: x${t.getEmployeeIntMultiplier()}\n`+` * Production Multiplier: x${t.getProductionMultiplier()}\n`+` * Sales Multiplier: x${t.getSalesMultiplier()}\n`+` * Scientific Research Multiplier: x${t.getScientificResearchMultiplier()}\n`+` * Storage Multiplier: x${t.getStorageMultiplier()}`})),r.appendChild(Object(_utils_uiHelpers_createPopupCloseButton__WEBPACK_IMPORTED_MODULE_24__.createPopupCloseButton)(researchTreeBox,{class:"std-button",display:"block",innerText:"Close"}))),researchTreeBoxOpened=!0},Industry.prototype.toJSON=function(){return Object(_utils_JSONReviver__WEBPACK_IMPORTED_MODULE_20__.Generic_toJSON)("Industry",this)},Industry.fromJSON=function(e){return Object(_utils_JSONReviver__WEBPACK_IMPORTED_MODULE_20__.Generic_fromJSON)(Industry,e.data)},_utils_JSONReviver__WEBPACK_IMPORTED_MODULE_20__.Reviver.constructors.Industry=Industry,Employee.prototype.process=function(e=1,t){var n=.003*e,a=n*Math.random();this.exp+=n,this.cyclesUntilRaise-=e,this.cyclesUntilRaise<=0&&(this.salary+=EmployeeRaiseAmount,this.cyclesUntilRaise+=CyclesPerEmployeeRaise);var r=n*Math.random();return this.pos===_EmployeePositions__WEBPACK_IMPORTED_MODULE_3__.EmployeePositions.Training&&(this.cha+=r,this.exp+=r,this.eff+=r),this.ene-=a,this.hap-=a,this.eneHappiness: "+Object(_utils_StringHelperFunctions__WEBPACK_IMPORTED_MODULE_25__.formatNumber)(this.hap,3)+"
        Energy: "+Object(_utils_StringHelperFunctions__WEBPACK_IMPORTED_MODULE_25__.formatNumber)(this.ene,3)+"
        Intelligence: "+Object(_utils_StringHelperFunctions__WEBPACK_IMPORTED_MODULE_25__.formatNumber)(i,3)+"
        Charisma: "+Object(_utils_StringHelperFunctions__WEBPACK_IMPORTED_MODULE_25__.formatNumber)(r,3)+"
        Experience: "+Object(_utils_StringHelperFunctions__WEBPACK_IMPORTED_MODULE_25__.formatNumber)(this.exp,3)+"
        Creativity: "+Object(_utils_StringHelperFunctions__WEBPACK_IMPORTED_MODULE_25__.formatNumber)(a,3)+"
        Efficiency: "+Object(_utils_StringHelperFunctions__WEBPACK_IMPORTED_MODULE_25__.formatNumber)(o,3)+"
        Salary: "+_ui_numeralFormat__WEBPACK_IMPORTED_MODULE_16__.numeralWrapper.format(this.sal,"$0.000a")+"/ s
        "}));var s=Object(_utils_uiHelpers_createElement__WEBPACK_IMPORTED_MODULE_22__.createElement)("select",{});for(var l in _EmployeePositions__WEBPACK_IMPORTED_MODULE_3__.EmployeePositions)_EmployeePositions__WEBPACK_IMPORTED_MODULE_3__.EmployeePositions.hasOwnProperty(l)&&s.add(Object(_utils_uiHelpers_createElement__WEBPACK_IMPORTED_MODULE_22__.createElement)("option",{text:_EmployeePositions__WEBPACK_IMPORTED_MODULE_3__.EmployeePositions[l],value:_EmployeePositions__WEBPACK_IMPORTED_MODULE_3__.EmployeePositions[l]}));s.addEventListener("change",()=>{this.pos=s.options[s.selectedIndex].value});for(var c=0;c=this.size},OfficeSpace.prototype.process=function(e=1,t){var n=t.industry;if(n.hasResearch("HRBuddy-Recruitment")&&!this.atCapacity()){const e=this.hireRandomEmployee();n.hasResearch("HRBuddy-Training")&&(e.pos=_EmployeePositions__WEBPACK_IMPORTED_MODULE_3__.EmployeePositions.Training)}this.maxEne=100,this.maxHap=100,this.maxMor=100,n.hasResearch("Go-Juice")&&(this.maxEne+=10),n.hasResearch("JoyWire")&&(this.maxHap+=10),n.hasResearch("Sti.mu")&&(this.maxMor+=10);var a=1;n.funds<0&&n.lastCycleRevenue<0?a=Math.pow(.99,e):n.funds>0&&n.lastCycleRevenue>0&&(a=Math.pow(1.01,e));const r=n.hasResearch("AutoBrew"),i=n.hasResearch("AutoPartyManager");var o=0;for(let t=0;tCharisma: "+Object(_utils_StringHelperFunctions__WEBPACK_IMPORTED_MODULE_25__.formatNumber)(t.cha,1)+"
        Experience: "+Object(_utils_StringHelperFunctions__WEBPACK_IMPORTED_MODULE_25__.formatNumber)(t.exp,1)+"
        Creativity: "+Object(_utils_StringHelperFunctions__WEBPACK_IMPORTED_MODULE_25__.formatNumber)(t.cre,1)+"
        Efficiency: "+Object(_utils_StringHelperFunctions__WEBPACK_IMPORTED_MODULE_25__.formatNumber)(t.eff,1)+"
        Salary: "+_ui_numeralFormat__WEBPACK_IMPORTED_MODULE_16__.numeralWrapper.format(t.sal,"$0.000a")+" s
        ",clickListener:()=>(n.hireEmployee(t,e),Object(_utils_uiHelpers_removeElementById__WEBPACK_IMPORTED_MODULE_30__.removeElementById)("cmpy-mgmt-hire-employee-popup"),!1)})},_=Object(_utils_uiHelpers_createElement__WEBPACK_IMPORTED_MODULE_22__.createElement)("a",{class:"a-link-button",innerText:"Cancel",float:"right",clickListener:()=>(Object(_utils_uiHelpers_removeElementById__WEBPACK_IMPORTED_MODULE_30__.removeElementById)("cmpy-mgmt-hire-employee-popup"),!1)}),g=[d,h(u,this),h(m,this),h(p,this),_];Object(_utils_uiHelpers_createPopup__WEBPACK_IMPORTED_MODULE_23__.createPopup)("cmpy-mgmt-hire-employee-popup",g)}},OfficeSpace.prototype.hireEmployee=function(e,t){var n=t.corporation,a=Object(_utils_YesNoBox__WEBPACK_IMPORTED_MODULE_31__.yesNoTxtInpBoxGetYesButton)(),r=Object(_utils_YesNoBox__WEBPACK_IMPORTED_MODULE_31__.yesNoTxtInpBoxGetNoButton)();a.innerHTML="Hire",r.innerHTML="Cancel",a.addEventListener("click",()=>{for(var t=Object(_utils_YesNoBox__WEBPACK_IMPORTED_MODULE_31__.yesNoTxtInpBoxGetInput)(),a=0;aObject(_utils_YesNoBox__WEBPACK_IMPORTED_MODULE_31__.yesNoTxtInpBoxClose)()),Object(_utils_YesNoBox__WEBPACK_IMPORTED_MODULE_31__.yesNoTxtInpBoxCreate)("Give your employee a nickname!")},OfficeSpace.prototype.hireRandomEmployee=function(){if(!this.atCapacity()&&null==document.getElementById("cmpy-mgmt-hire-employee-popup")){var e=Object(_utils_helpers_getRandomInt__WEBPACK_IMPORTED_MODULE_26__.getRandomInt)(76,100)/100,t=Object(_utils_helpers_getRandomInt__WEBPACK_IMPORTED_MODULE_26__.getRandomInt)(50,100),n=Object(_utils_helpers_getRandomInt__WEBPACK_IMPORTED_MODULE_26__.getRandomInt)(50,100),a=Object(_utils_helpers_getRandomInt__WEBPACK_IMPORTED_MODULE_26__.getRandomInt)(50,100),r=Object(_utils_helpers_getRandomInt__WEBPACK_IMPORTED_MODULE_26__.getRandomInt)(50,100),i=Object(_utils_helpers_getRandomInt__WEBPACK_IMPORTED_MODULE_26__.getRandomInt)(50,100),o=new Employee({intelligence:t*e,charisma:n*e,experience:a*e,creativity:r*e,efficiency:i*e,salary:EmployeeSalaryMultiplier*(t+n+a+r+i)*e}),s=Object(_utils_StringHelperFunctions__WEBPACK_IMPORTED_MODULE_25__.generateRandomString)(7);for(let e=0;e=CyclesPerIndustryStateCycle){const e=this.getState(),t=1,n=t*CyclesPerIndustryStateCycle;if(this.storedCycles-=n,this.divisions.forEach(n=>{n.process(t,e,this)}),this.shareSaleCooldown>0&&(this.shareSaleCooldown-=n),this.issueNewSharesCooldown>0&&(this.issueNewSharesCooldown-=n),"START"===e){this.revenue=new decimal_js__WEBPACK_IMPORTED_MODULE_37__.a(0),this.expenses=new decimal_js__WEBPACK_IMPORTED_MODULE_37__.a(0),this.divisions.forEach(e=>{e.lastCycleRevenue!==-1/0&&e.lastCycleRevenue!==1/0&&e.lastCycleExpenses!==-1/0&&e.lastCycleExpenses!==1/0&&(this.revenue=this.revenue.plus(e.lastCycleRevenue),this.expenses=this.expenses.plus(e.lastCycleExpenses))});const e=this.revenue.minus(this.expenses).times(t*SecsPerMarketCycle);if(isNaN(this.funds)&&(Object(_utils_DialogBox__WEBPACK_IMPORTED_MODULE_19__.dialogBoxCreate)("There was an error calculating your Corporations funds and they got reset to 0. This is a bug. Please report to game developer.

        (Your funds have been set to $150b for the inconvenience)"),this.funds=new decimal_js__WEBPACK_IMPORTED_MODULE_37__.a(15e10)),this.dividendPercentage>0&&e>0)if(isNaN(this.dividendPercentage)||this.dividendPercentage<0||this.dividendPercentage>DividendMaxPercentage)console.error(`Invalid Corporation dividend percentage: ${this.dividendPercentage}`);else{const t=this.dividendPercentage/100*e,n=e-t,a=t/this.totalShares,r=this.numShares*a*(1-this.dividendTaxPercentage/100);_Player__WEBPACK_IMPORTED_MODULE_15__.Player.gainMoney(r),_Player__WEBPACK_IMPORTED_MODULE_15__.Player.recordMoneySource(r,"corporation"),this.funds=this.funds.plus(n)}else this.funds=this.funds.plus(e);this.updateSharePrice()}this.state.nextState(),_ui_navigationTracking__WEBPACK_IMPORTED_MODULE_17__.routing.isOn(_ui_navigationTracking__WEBPACK_IMPORTED_MODULE_17__.Page.Corporation)&&this.rerender()}},Corporation.prototype.determineValuation=function(){var e,t=this.revenue.minus(this.expenses).toNumber();return this.public?(this.dividendPercentage>0&&(t*=(100-this.dividendPercentage)/100),e=this.funds.toNumber()+85e3*t,e*=Math.pow(1.1,this.divisions.length),e=Math.max(e,0)):(e=1e10+Math.max(this.funds.toNumber(),0)/3,t>0?(e+=315e3*t,e*=Math.pow(1.1,this.divisions.length)):e=1e10*Math.pow(1.1,this.divisions.length),e-=e%1e6),e*_BitNode_BitNodeMultipliers__WEBPACK_IMPORTED_MODULE_11__.BitNodeMultipliers.CorporationValuation},Corporation.prototype.getInvestment=function(){var e,t=this.determineValuation();let n=4;switch(this.fundingRound){case 0:e=.1,n=4;break;case 1:e=.35,n=3;break;case 2:e=.25,n=3;break;case 3:e=.2,n=2.5;break;case 4:return}var a=t*e*n,r=Math.floor(INITIALSHARES*e),i=Object(_utils_YesNoBox__WEBPACK_IMPORTED_MODULE_31__.yesNoBoxGetYesButton)(),o=Object(_utils_YesNoBox__WEBPACK_IMPORTED_MODULE_31__.yesNoBoxGetNoButton)();i.innerHTML="Accept",o.innerHML="Reject",i.addEventListener("click",()=>(++this.fundingRound,this.funds=this.funds.plus(a),this.numShares-=r,this.rerender(),Object(_utils_YesNoBox__WEBPACK_IMPORTED_MODULE_31__.yesNoBoxClose)())),o.addEventListener("click",()=>Object(_utils_YesNoBox__WEBPACK_IMPORTED_MODULE_31__.yesNoBoxClose)()),Object(_utils_YesNoBox__WEBPACK_IMPORTED_MODULE_31__.yesNoBoxCreate)("An investment firm has offered you "+_ui_numeralFormat__WEBPACK_IMPORTED_MODULE_16__.numeralWrapper.format(a,"$0.000a")+" in funding in exchange for a "+_ui_numeralFormat__WEBPACK_IMPORTED_MODULE_16__.numeralWrapper.format(100*e,"0.000a")+"% stake in the company ("+_ui_numeralFormat__WEBPACK_IMPORTED_MODULE_16__.numeralWrapper.format(r,"0.000a")+" shares).

        Do you accept or reject this offer?

        Hint: Investment firms will offer more money if your corporation is turning a profit")},Corporation.prototype.goPublic=function(){var e,t=this.determineValuation()/this.totalShares,n=Object(_utils_uiHelpers_createElement__WEBPACK_IMPORTED_MODULE_22__.createElement)("p",{innerHTML:"Enter the number of shares you would like to issue for your IPO. These shares will be publicly sold and you will no longer own them. Your Corporation will receive "+_ui_numeralFormat__WEBPACK_IMPORTED_MODULE_16__.numeralWrapper.format(t,"$0.000a")+" per share (the IPO money will be deposited directly into your Corporation's funds).

        You have a total of "+_ui_numeralFormat__WEBPACK_IMPORTED_MODULE_16__.numeralWrapper.format(this.numShares,"0.000a")+" of shares that you can issue."}),a=Object(_utils_uiHelpers_createElement__WEBPACK_IMPORTED_MODULE_22__.createElement)("input",{type:"number",placeholder:"Shares to issue",onkeyup:t=>{t.preventDefault(),t.keyCode===_utils_helpers_keyCodes__WEBPACK_IMPORTED_MODULE_28__.KEY.ENTER&&e.click()}}),r=Object(_utils_uiHelpers_createElement__WEBPACK_IMPORTED_MODULE_22__.createElement)("br",{});e=Object(_utils_uiHelpers_createElement__WEBPACK_IMPORTED_MODULE_22__.createElement)("a",{class:"a-link-button",innerText:"Go Public",clickListener:()=>{var e=Math.round(a.value),t=this.determineValuation()/this.totalShares;return isNaN(e)?(Object(_utils_DialogBox__WEBPACK_IMPORTED_MODULE_19__.dialogBoxCreate)("Invalid value for number of issued shares"),!1):e>this.numShares?(Object(_utils_DialogBox__WEBPACK_IMPORTED_MODULE_19__.dialogBoxCreate)("Error: You don't have that many shares to issue!"),!1):(this.public=!0,this.sharePrice=t,this.issuedShares=e,this.numShares-=e,this.funds=this.funds.plus(e*t),this.rerender(),Object(_utils_uiHelpers_removeElementById__WEBPACK_IMPORTED_MODULE_30__.removeElementById)("cmpy-mgmt-go-public-popup"),Object(_utils_DialogBox__WEBPACK_IMPORTED_MODULE_19__.dialogBoxCreate)(`You took your ${this.name} public and earned `+`${_ui_numeralFormat__WEBPACK_IMPORTED_MODULE_16__.numeralWrapper.formatMoney(e*t)} in your IPO`),!1)}});var i=Object(_utils_uiHelpers_createElement__WEBPACK_IMPORTED_MODULE_22__.createElement)("a",{class:"a-link-button",innerText:"Cancel",clickListener:()=>(Object(_utils_uiHelpers_removeElementById__WEBPACK_IMPORTED_MODULE_30__.removeElementById)("cmpy-mgmt-go-public-popup"),!1)});Object(_utils_uiHelpers_createPopup__WEBPACK_IMPORTED_MODULE_23__.createPopup)("cmpy-mgmt-go-public-popup",[n,r,a,e,i])},Corporation.prototype.getTargetSharePrice=function(){return this.determineValuation()/(2*(this.totalShares-this.numShares)+1)},Corporation.prototype.updateSharePrice=function(){const e=this.getTargetSharePrice();this.sharePrice<=e?this.sharePrice*=1+.01*Math.random():this.sharePrice*=1-.01*Math.random(),this.sharePrice<=.01&&(this.sharePrice=.01)},Corporation.prototype.immediatelyUpdateSharePrice=function(){this.sharePrice=this.getTargetSharePrice()},Corporation.prototype.calculateShareSale=function(e){let t=e,n=this.shareSalesUntilPriceUpdate,a=this.sharePrice,r=0,i=0;const o=Math.ceil(e/SHARESPERPRICEUPDATE);if(!(isNaN(o)||o>1e7)){for(let e=0;e3600?`${Math.floor(t/3600)} hour(s)`:t>60?`${Math.floor(t/60)} minute(s)`:`${Math.floor(t)} second(s)`},Corporation.prototype.unlock=function(e){const t=e[0],n=e[1];for(;this.unlockUpgrades.length<=t;)this.unlockUpgrades.push(0);this.funds.lt(n)?Object(_utils_DialogBox__WEBPACK_IMPORTED_MODULE_19__.dialogBoxCreate)("You don't have enough funds to unlock this!"):(this.unlockUpgrades[t]=1,this.funds=this.funds.minus(n),5===t?this.dividendTaxPercentage-=5:6===t&&(this.dividendTaxPercentage-=10))},Corporation.prototype.upgrade=function(e){for(var t=e[0],n=e[1],a=e[2],r=e[3];this.upgrades.length<=t;)this.upgrades.push(0);for(;this.upgradeMultipliers.length<=t;)this.upgradeMultipliers.push(1);var i=n*Math.pow(a,this.upgrades[t]);if(this.funds.lt(i))Object(_utils_DialogBox__WEBPACK_IMPORTED_MODULE_19__.dialogBoxCreate)("You don't have enough funds to purchase this!");else if(++this.upgrades[t],this.funds=this.funds.minus(i),this.upgradeMultipliers[t]=1+this.upgrades[t]*r,1===t)for(var o=0;oh.Start&&(_.currStep-=1);f()}(),!1}),Object(c.clearEventListeners)("interactive-tutorial-next").addEventListener("click",function(){return y(),!1}),f()}function f(){if(!_.isRunning)return;const e=Object(c.clearEventListeners)("terminal-menu-link"),t=Object(c.clearEventListeners)("stats-menu-link"),n=Object(c.clearEventListeners)("active-scripts-menu-link"),r=Object(c.clearEventListeners)("hacknet-nodes-menu-link"),i=Object(c.clearEventListeners)("city-menu-link"),o=Object(c.clearEventListeners)("tutorial-menu-link");e.removeAttribute("class"),t.removeAttribute("class"),n.removeAttribute("class"),r.removeAttribute("class"),i.removeAttribute("class"),o.removeAttribute("class");const s=document.getElementById("interactive-tutorial-next");switch(_.currStep){case h.Start:a.Engine.loadTerminalContent(),v("Welcome to Bitburner, a cyberpunk-themed incremental RPG! The game takes place in a dark, dystopian future... The year is 2077...

        This tutorial will show you the basics of the game. You may skip the tutorial at any time."),s.style.display="inline-block";break;case h.GoToCharacterPage:a.Engine.loadTerminalContent(),v("Let's start by heading to the Stats page. Click the Stats tab on the main navigation menu (left-hand side of the screen)"),s.style.display="none",t.setAttribute("class","flashing-button"),t.addEventListener("click",function(){return a.Engine.loadCharacterContent(),y(),!1});break;case h.CharacterPage:a.Engine.loadCharacterContent(),v("The Stats page shows a lot of important information about your progress, such as your skills, money, and bonuses. "),s.style.display="inline-block";break;case h.CharacterGoToTerminalPage:a.Engine.loadCharacterContent(),v("Let's head to your computer's terminal by clicking the Terminal tab on the main navigation menu."),s.style.display="none",e.setAttribute("class","flashing-button"),e.addEventListener("click",function(){return a.Engine.loadTerminalContent(),y(),!1});break;case h.TerminalIntro:a.Engine.loadTerminalContent(),v("The Terminal is used to interface with your home computer as well as all of the other machines around the world."),s.style.display="inline-block";break;case h.TerminalHelp:a.Engine.loadTerminalContent(),v("Let's try it out. Start by entering the help command into the Terminal (Don't forget to press Enter after typing the command)"),s.style.display="none";break;case h.TerminalLs:a.Engine.loadTerminalContent(),v("The help command displays a list of all available Terminal commands, how to use them, and a description of what they do.

        Let's try another command. Enter the ls command."),s.style.display="none";break;case h.TerminalScan:a.Engine.loadTerminalContent(),v(" ls is a basic command that shows files on the computer. Right now, it shows that you have a program called NUKE.exe on your computer. We'll get to what this does later.

        Using your home computer's terminal, you can connect to other machines throughout the world. Let's do that now by first entering the scan command."),s.style.display="none";break;case h.TerminalScanAnalyze1:a.Engine.loadTerminalContent(),v("The scan command shows all available network connections. In other words, it displays a list of all servers that can be connected to from your current machine. A server is identified by its hostname.

        That's great and all, but there's so many servers. Which one should you go to? The scan-analyze command gives some more detailed information about servers on the network. Try it now!"),s.style.display="none";break;case h.TerminalScanAnalyze2:a.Engine.loadTerminalContent(),v("You just ran scan-analyze with a depth of one. This command shows more detailed information about each server that you can connect to (servers that are a distance of one node away).

        It is also possible to run scan-analyze with a higher depth. Let's try a depth of two with the following command: scan-analyze 2."),s.style.display="none";break;case h.TerminalConnect:a.Engine.loadTerminalContent(),v("Now you can see information about all servers that are up to two nodes away, as well as figure out how to navigate to those servers through the network. You can only connect to a server that is one node away. To connect to a machine, use the connect [hostname] command.

        From the results of the scan-analyze command, we can see that the n00dles server is only one node away. Let's connect so it now using: connect n00dles"),s.style.display="none";break;case h.TerminalAnalyze:a.Engine.loadTerminalContent(),v("You are now connected to another machine! What can you do now? You can hack it!

        In the year 2077, currency has become digital and decentralized. People and corporations store their money on servers and computers. Using your hacking abilities, you can hack servers to steal money and gain experience.

        Before you try to hack a server, you should run diagnostics using the analyze command."),s.style.display="none";break;case h.TerminalNuke:a.Engine.loadTerminalContent(),v("When the analyze command finishes running it will show useful information about hacking the server.

        For this server, the required hacking skill is only 1, which means you can hack it right now. However, in order to hack a server you must first gain root access. The NUKE.exe program that we saw earlier on your home computer is a virus that will grant you root access to a machine if there are enough open ports.

        The analyze results shows that there do not need to be any open ports on this machine for the NUKE virus to work, so go ahead and run the virus using the run NUKE.exe command."),s.style.display="none";break;case h.TerminalManualHack:a.Engine.loadTerminalContent(),v("You now have root access! You can hack the server using the hack command. Try doing that now."),s.style.display="none";break;case h.TerminalHackingMechanics:a.Engine.loadTerminalContent(),v("You are now attempting to hack the server. Performing a hack takes time and only has a certain percentage chance of success. This time and success chance is determined by a variety of factors, including your hacking skill and the server's security level.

        If your attempt to hack the server is successful, you will steal a certain percentage of the server's total money. This percentage is affected by your hacking skill and the server's security level.

        The amount of money on a server is not limitless. So, if you constantly hack a server and deplete its money, then you will encounter diminishing returns in your hacking."),s.style.display="inline-block";break;case h.TerminalCreateScript:a.Engine.loadTerminalContent(),v("Hacking is the core mechanic of the game and is necessary for progressing. However, you don't want to be hacking manually the entire time. You can automate your hacking by writing scripts!

        To create a new script or edit an existing one, you can use the nano command. Scripts must end with the .script extension. Let's make a script now by entering nano n00dles.script after the hack command finishes running (Sidenote: Pressing ctrl + c will end a command like hack early)"),s.style.display="none";break;case h.TerminalTypeScript:a.Engine.loadScriptEditorContent("n00dles.script",""),v("This is the script editor. You can use it to program your scripts. Scripts are written in a simplified version of javascript. Copy and paste the following code into the script editor:

        while(true) {\n  hack('n00dles');\n}
        For anyone with basic programming experience, this code should be straightforward. This script will continuously hack the n00dles server.

        To save and close the script editor, press the button in the bottom left, or press ctrl + b."),s.style.display="none";break;case h.TerminalFree:a.Engine.loadTerminalContent(),v("Now we'll run the script. Scripts require a certain amount of RAM to run, and can be run on any machine which you have root access to. Different servers have different amounts of RAM. You can also purchase more RAM for your home server.

        To check how much RAM is available on this machine, enter the free command."),s.style.display="none";break;case h.TerminalRunScript:a.Engine.loadTerminalContent(),v("We have 4GB of free RAM on this machine, which is enough to run our script. Let's run our script using run n00dles.script."),s.style.display="none";break;case h.TerminalGoToActiveScriptsPage:a.Engine.loadTerminalContent(),v("Your script is now running! It will continuously run in the background and will automatically stop if the code ever completes (the n00dles.script will never complete because it runs an infinite loop).

        These scripts can passively earn you income and hacking experience. Your scripts will also earn money and experience while you are offline, although at a slightly slower rate.

        Let's check out some statistics for our running scripts by clicking the Active Scripts link in the main navigation menu."),s.style.display="none",n.setAttribute("class","flashing-button"),n.addEventListener("click",function(){return a.Engine.loadActiveScriptsContent(),y(),!1});break;case h.ActiveScriptsPage:a.Engine.loadActiveScriptsContent(),v("This page displays information about all of your scripts that are running across every server. You can use this to gauge how well your scripts are doing. Let's go back to the Terminal"),s.style.display="none",e.setAttribute("class","flashing-button"),e.addEventListener("click",function(){return a.Engine.loadTerminalContent(),y(),!1});break;case h.ActiveScriptsToTerminal:a.Engine.loadTerminalContent(),v("One last thing about scripts, each active script contains logs that detail what it's doing. We can check these logs using the tail command. Do that now for the script we just ran by typing tail n00dles.script"),s.style.display="none";break;case h.TerminalTailScript:a.Engine.loadTerminalContent(),v("The log for this script won't show much right now (it might show nothing at all) because it just started running...but check back again in a few minutes!

        This covers the basics of hacking. To learn more about writing scripts, select the Tutorial link in the main navigation menu to look at the documentation. If you are an experienced JavaScript developer, I would highly suggest you check out the section on NetscriptJS/Netscript 2.0, it's faster and more powerful.

        For now, let's move on to something else!"),s.style.display="inline-block";break;case h.GoToHacknetNodesPage:a.Engine.loadTerminalContent(),v("Hacking is not the only way to earn money. One other way to passively earn money is by purchasing and upgrading Hacknet Nodes. Let's go to the Hacknet page through the main navigation menu now."),s.style.display="none",r.setAttribute("class","flashing-button"),r.addEventListener("click",function(){return a.Engine.loadHacknetNodesContent(),y(),!1});break;case h.HacknetNodesIntroduction:a.Engine.loadHacknetNodesContent(),v("here you can purchase new Hacknet Nodes and upgrade your existing ones. Let's purchase a new one now."),s.style.display="none";break;case h.HacknetNodesGoToWorldPage:a.Engine.loadHacknetNodesContent(),v("You just purchased a Hacknet Node! This Hacknet Node will passively earn you money over time, both online and offline. When you get enough money, you can upgrade your newly-purchased Hacknet Node below.

        Let's go to the City page through the main navigation menu."),s.style.display="none",i.setAttribute("class","flashing-button"),i.addEventListener("click",function(){return a.Engine.loadLocationContent(),y(),!1});break;case h.WorldDescription:a.Engine.loadLocationContent(),v("This page lists all of the different locations you can currently travel to. Each location has something that you can do. There's a lot of content out in the world, make sure you explore and discover!

        Lastly, click on the Tutorial link in the main navigation menu."),s.style.display="none",o.setAttribute("class","flashing-button"),o.addEventListener("click",function(){return a.Engine.loadTutorialContent(),y(),!1});break;case h.TutorialPageInfo:a.Engine.loadTutorialContent(),v("This page contains a lot of different documentation about the game's content and mechanics. I know it's a lot, but I highly suggest you read (or at least skim) through this before you start playing. That's the end of the tutorial. Hope you enjoy the game!"),s.style.display="inline-block",s.innerHTML="Finish Tutorial";break;case h.End:b();break;default:throw new Error("Invalid tutorial step")}!0===_.stepIsDone[_.currStep]&&(s.style.display="inline-block")}function y(){_.currStep===h.GoToCharacterPage&&document.getElementById("stats-menu-link").removeAttribute("class"),_.currStep===h.CharacterGoToTerminalPage&&document.getElementById("terminal-menu-link").removeAttribute("class"),_.currStep===h.TerminalGoToActiveScriptsPage&&document.getElementById("active-scripts-menu-link").removeAttribute("class"),_.currStep===h.ActiveScriptsPage&&document.getElementById("terminal-menu-link").removeAttribute("class"),_.currStep===h.GoToHacknetNodesPage&&document.getElementById("hacknet-nodes-menu-link").removeAttribute("class"),_.currStep===h.HacknetNodesGoToWorldPage&&document.getElementById("city-menu-link").removeAttribute("class"),_.currStep===h.WorldDescription&&document.getElementById("tutorial-menu-link").removeAttribute("class"),_.stepIsDone[_.currStep]=!0,_.currStep
        Getting Started GuideDocumentation

        The Beginner's Guide to Hacking was added to your home computer! It contains some tips/pointers for starting out with the game. To read it, go to Terminal and enter

        cat "+s.LiteratureNames.HackersStartingHandbook}),n=Object(u.createElement)("a",{class:"a-link-button",float:"right",padding:"6px",innerText:"Got it!",clickListener:()=>{Object(p.removeElementById)(e)}});Object(m.createPopup)(e,[t,n]),r.Player.getHomeComputer().messages.push(s.LiteratureNames.HackersStartingHandbook)}let E=null;function v(e){E.innerHTML=e,E.parentElement.scrollTop=0}!function(){document.addEventListener("DOMContentLoaded",function e(){E=document.getElementById("interactive-tutorial-text"),document.removeEventListener("DOMContentLoaded",e)})}()},,function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.clearEventListeners=void 0;const a=n(201);t.clearEventListeners=function(e){try{let t;const n=(t="string"==typeof e?a.getElementById(e):e).cloneNode(!0);return null!==t.parentNode&&t.parentNode.replaceChild(n,t),n}catch(e){return console.error(e),null}}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.createPopup=void 0;const a=n(7),r=n(201);t.createPopup=function(e,t,n={}){const i=a.createElement("div",{class:"popup-box-container",display:"flex",id:e}),o=a.createElement("div",{class:"popup-box-content",id:`${e}-content`});for(const e of t)o.appendChild(e);return n.backgroundColor&&(o.style.backgroundColor=n.backgroundColor),i.appendChild(o),r.getElementById("entire-game-container").appendChild(i),i}},,function(e,t,n){"use strict";var a=this&&this.__createBinding||(Object.create?function(e,t,n,a){void 0===a&&(a=n),Object.defineProperty(e,a,{enumerable:!0,get:function(){return t[n]}})}:function(e,t,n,a){void 0===a&&(a=n),e[a]=t[n]}),r=this&&this.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}:function(e,t){e.default=t}),i=this&&this.__importStar||function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var n in e)"default"!==n&&Object.prototype.hasOwnProperty.call(e,n)&&a(t,e,n);return r(t,e),t};Object.defineProperty(t,"__esModule",{value:!0}),t.displayStockMarketContent=t.processStockPrices=t.stockMarketCycle=t.initSymbolToStockMap=t.initStockMarket=t.deleteStockMarket=t.loadStockMarket=t.cancelOrder=t.placeOrder=t.SymbolToStockMap=t.StockMarket=void 0;const o=n(182),s=n(971),l=n(970),c=n(202),u=n(969),m=n(968),p=n(122),d=n(107),h=n(292),_=n(967),g=n(11),f=n(1),y=n(493),b=n(16),E=n(3),v=n(12),k=n(26),C=i(n(0)),P=i(n(25));function S(e,n,a,r,i,o=null){if(!(e instanceof c.Stock))return o?o.log("placeOrder",`Invalid stock: '${e}'`):v.dialogBoxCreate("ERROR: Invalid stock passed to placeOrder() function"),!1;if("number"!=typeof n||"number"!=typeof a)return o?o.log("placeOrder",`Invalid arguments: shares='${n}' price='${a}'`):v.dialogBoxCreate("ERROR: Invalid numeric value provided for either 'shares' or 'price' argument"),!1;const u=new s.Order(e.symbol,n,a,r,i);if(null==t.StockMarket.Orders){const e={};for(const n in t.StockMarket){const a=t.StockMarket[n];a instanceof c.Stock&&(e[a.symbol]=[])}t.StockMarket.Orders=e}t.StockMarket.Orders[e.symbol].push(u);const m={rerenderFn:D,stockMarket:t.StockMarket,symbolToStockMap:t.SymbolToStockMap};return l.processOrders(e,u.type,u.pos,m),D(),!0}function O(e,n=null){if(null==t.StockMarket.Orders)return!1;if(e.order&&e.order instanceof s.Order){const n=e.order,a=t.StockMarket.Orders[n.stockSymbol];for(let e=0;e=n.cap&&(i=.1,n.b=!1),isNaN(i)&&(i=.5);const o=Math.random(),s={rerenderFn:D,stockMarket:t.StockMarket,symbolToStockMap:t.SymbolToStockMap};o1?1:i<0?0:i},t.calculateHackingExpGain=function(e,t){null==e.baseDifficulty&&(e.baseDifficulty=e.hackDifficulty);let n=3;return(n+=e.baseDifficulty*t.hacking_exp_mult*.3)*a.BitNodeMultipliers.HackExpGain},t.calculatePercentMoneyHacked=function(e,t){const n=(100-e.hackDifficulty)/100*((t.hacking_skill-(e.requiredHackingSkill-1))/t.hacking_skill)*t.hacking_money_mult/240;return n<0?0:n>1?1:n*a.BitNodeMultipliers.ScriptHackMoney},t.calculateHackingTime=i,t.calculateGrowTime=function(e,t){return 3.2*i(e,t)},t.calculateWeakenTime=function(e,t){return 4*i(e,t)}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.CompanyPositions=void 0;const a=n(1205),r=n(294);t.CompanyPositions={},a.companyPositionMetadata.forEach(e=>{!function(e){null!=t.CompanyPositions[e.name]&&console.warn(`Duplicate Company Position being defined: ${e.name}`),t.CompanyPositions[e.name]=new r.CompanyPosition(e)}(e)})},,,function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.removeChildrenFromElement=void 0;const a=n(65),r=n(201);t.removeChildrenFromElement=function(e){if(null!==e)try{const t=a.isString(e)?r.getElementById(e):e;if(t instanceof Element)for(;null!==t.firstChild;)t.removeChild(t.firstChild)}catch(e){return void console.debug(e)}}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.isString=void 0,t.isString=function(e){return"string"==typeof e||e instanceof String}},,function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.OwnedAugmentationsOrderSetting=t.PurchaseAugmentationsOrderSetting=t.EditorSetting=t.CodeMirrorThemeSetting=t.CodeMirrorKeybindingSetting=t.AceKeybindingSetting=void 0,function(e){e.Ace="ace",e.Emacs="emacs",e.Vim="vim"}(t.AceKeybindingSetting||(t.AceKeybindingSetting={})),function(e){e.Default="default",e.Emacs="emacs",e.Sublime="sublime",e.Vim="vim"}(t.CodeMirrorKeybindingSetting||(t.CodeMirrorKeybindingSetting={})),function(e){e.Monokai="monokai",e.Day_3024="3024-day",e.Night_3024="3024-night",e.abcdef="abcdef",e.Ambiance_mobile="ambiance-mobile",e.Ambiance="ambiance",e.Base16_dark="base16-dark",e.Base16_light="base16-light",e.Bespin="bespin",e.Blackboard="blackboard",e.Cobalt="cobalt",e.Colorforth="colorforth",e.Darcula="darcula",e.Dracula="dracula",e.Duotone_dark="duotone-dark",e.Duotone_light="duotone-light",e.Eclipse="eclipse",e.Elegant="elegant",e.Erlang_dark="erlang-dark",e.Gruvbox_dark="gruvbox-dark",e.Hopscotch="hopscotch",e.Icecoder="icecoder",e.Idea="idea",e.Isotope="isotope",e.Lesser_dark="lesser-dark",e.Liquibyte="liquibyte",e.Lucario="lucario",e.Material="material",e.Mbo="mbo",e.Mdn_like="mdn-like",e.Midnight="midnight",e.Neat="neat",e.Neo="neo",e.Night="night",e.Oceanic_next="oceanic-next",e.Panda_syntax="panda-syntax",e.Paraiso_dark="paraiso-dark",e.Paraiso_light="paraiso-light",e.Pastel_on_dark="pastel-on-dark",e.Railscasts="railscasts",e.Rubyblue="rubyblue",e.Seti="seti",e.Shadowfox="shadowfox",e.Solarized="solarized",e.SolarizedDark="solarized dark",e.ssms="ssms",e.The_matrix="the-matrix",e.Tomorrow_night_bright="tomorrow-night-bright",e.Tomorrow_night_eighties="tomorrow-night-eighties",e.Ttcn="ttcn",e.Twilight="twilight",e.Vibrant_ink="vibrant-ink",e.xq_dark="xq-dark",e.xq_light="xq-light",e.Yeti="yeti",e.Zenburn="zenburn"}(t.CodeMirrorThemeSetting||(t.CodeMirrorThemeSetting={})),function(e){e.Ace="Ace",e.CodeMirror="CodeMirror"}(t.EditorSetting||(t.EditorSetting={})),function(e){e[e.Cost=0]="Cost",e[e.Default=1]="Default",e[e.Reputation=2]="Reputation"}(t.PurchaseAugmentationsOrderSetting||(t.PurchaseAugmentationsOrderSetting={})),function(e){e[e.Alphabetically=0]="Alphabetically",e[e.AcquirementTime=1]="AcquirementTime"}(t.OwnedAugmentationsOrderSetting||(t.OwnedAugmentationsOrderSetting={}))},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.PartTimeCompanyPositions=t.BusinessConsultantCompanyPositions=t.SoftwareConsultantCompanyPositions=t.MiscCompanyPositions=t.AgentCompanyPositions=t.SecurityCompanyPositions=t.BusinessCompanyPositions=t.NetworkEngineerCompanyPositions=t.SecurityEngineerCompanyPositions=t.ITCompanyPositions=t.SoftwareCompanyPositions=void 0,t.SoftwareCompanyPositions=["Software Engineering Intern","Junior Software Engineer","Senior Software Engineer","Lead Software Developer","Head of Software","Head of Engineering","Vice President of Technology","Chief Technology Officer"],t.ITCompanyPositions=["IT Intern","IT Analyst","IT Manager","Systems Administrator"],t.SecurityEngineerCompanyPositions=["Security Engineer"],t.NetworkEngineerCompanyPositions=["Network Engineer","Network Administrator"],t.BusinessCompanyPositions=["Business Intern","Business Analyst","Business Manager","Operations Manager","Chief Financial Officer","Chief Executive Officer"],t.SecurityCompanyPositions=["Police Officer","Police Chief","Security Guard","Security Officer","Security Supervisor","Head of Security"],t.AgentCompanyPositions=["Field Agent","Secret Agent","Special Operative"],t.MiscCompanyPositions=["Waiter","Employee"],t.SoftwareConsultantCompanyPositions=["Software Consultant","Senior Software Consultant"],t.BusinessConsultantCompanyPositions=["Business Consultant","Senior Business Consultant"],t.PartTimeCompanyPositions=["Part-time Waiter","Part-time Employee"]},function(e,t,n){"use strict";var a=this&&this.__createBinding||(Object.create?function(e,t,n,a){void 0===a&&(a=n),Object.defineProperty(e,a,{enumerable:!0,get:function(){return t[n]}})}:function(e,t,n,a){void 0===a&&(a=n),e[a]=t[n]}),r=this&&this.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}:function(e,t){e.default=t}),i=this&&this.__importStar||function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var n in e)"default"!==n&&Object.prototype.hasOwnProperty.call(e,n)&&a(t,e,n);return r(t,e),t};Object.defineProperty(t,"__esModule",{value:!0}),t.StdButton=void 0;const o=i(n(0));t.StdButton=function(e){const t=null!=e.tooltip&&""!==e.tooltip;let n,a=e.disabled?"std-button-disabled":"std-button";if(t&&(a+=" tooltip"),"string"==typeof e.addClasses&&(a+=` ${e.addClasses}`),t)if("string"==typeof e.tooltip){const t={__html:e.tooltip};n=o.createElement("span",{className:"tooltiptext",dangerouslySetInnerHTML:t})}else n=o.createElement("span",{className:"tooltiptext"},e.tooltip);return o.createElement("button",{className:a,id:e.id,onClick:e.onClick,style:e.style},e.text,t&&n)}},function(e,t,n){"use strict";var a=this&&this.__createBinding||(Object.create?function(e,t,n,a){void 0===a&&(a=n),Object.defineProperty(e,a,{enumerable:!0,get:function(){return t[n]}})}:function(e,t,n,a){void 0===a&&(a=n),e[a]=t[n]}),r=this&&this.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}:function(e,t){e.default=t}),i=this&&this.__importStar||function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var n in e)"default"!==n&&Object.prototype.hasOwnProperty.call(e,n)&&a(t,e,n);return r(t,e),t};Object.defineProperty(t,"__esModule",{value:!0}),t.Reputation=void 0;const o=i(n(0)),s=n(3);t.Reputation=function(e){return o.createElement("span",{className:"reputation samefont"},"number"==typeof e?s.numeralWrapper.formatReputation(e):e)}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.exceptionAlert=void 0;const a=n(12);t.exceptionAlert=function(e){console.error(e),a.dialogBoxCreate("Caught an exception: "+e+"

        Filename: "+(e.fileName||"UNKNOWN FILE NAME")+"

        Line Number: "+(e.lineNumber||"UNKNOWN LINE NUMBER")+"

        This is a bug, please report to game developer with this message as well as details about how to reproduce the bug.

        If you want to be safe, I suggest refreshing the game WITHOUT saving so that your safe doesn't get corrupted",!1)}},,function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.createPopupCloseButton=void 0;const a=n(7),r=n(146);t.createPopupCloseButton=function(e,t){const n=a.createElement("button",{class:t.class?t.class:"popup-box-button",display:t.display?t.display:"inline-block",innerText:null==t.innerText?"Cancel":t.innerText});function i(e){27===e.keyCode&&n.click()}return n.addEventListener("click",()=>{if(e instanceof Element)r.removeElement(e);else try{const t=document.getElementById(e);t instanceof Element&&r.removeElement(t)}catch(e){console.error(`createPopupCloseButton() threw: ${e}`)}return document.removeEventListener("keydown",i),!1}),document.addEventListener("keydown",i),n}},,function(e,t,n){"use strict";n.r(t),n.d(t,"inviteToFaction",function(){return M}),n.d(t,"joinFaction",function(){return T}),n.d(t,"startHackingMission",function(){return x}),n.d(t,"displayFactionContent",function(){return w}),n.d(t,"purchaseAugmentationBoxCreate",function(){return A}),n.d(t,"hasAugmentationPrereqs",function(){return R}),n.d(t,"purchaseAugmentation",function(){return N}),n.d(t,"getNextNeurofluxLevel",function(){return D}),n.d(t,"processPassiveFactionRepGain",function(){return B});var a=n(0),r=n.n(a),i=n(25),o=n.n(i),s=n(773),l=n(15),c=n(90),u=n(204),m=n(4),p=n(24),d=n(11),h=n(18),_=n(121),g=n(19),f=n(93),y=n(1),b=n(17),E=n(119),v=n(46),k=n(16),C=n(12),P=n(772),S=n(31),O=n(58);function M(e){b.Settings.SuppressFactionInvites?(e.alreadyInvited=!0,y.Player.factionInvitations.push(e.name),k.routing.isOn(k.Page.Factions)&&h.Engine.loadFactionsContent()):Object(P.a)(e)}function T(e){if(e.isMember)return;e.isMember=!0,y.Player.factions.push(e.name);const t=e.getInfo();for(const e in t.enemies){const n=t.enemies[e];g.Factions[n]instanceof _.Faction&&(g.Factions[n].isBanned=!0)}for(var n=0;n0)for(let n=0;n0){if("unalias"===a[0]||"alias"===a[0])return a.join(" ");let e=!0,o=0;for(;e&&o<10;){o++,e=!1;const s=null===(t=r(a[0]))||void 0===t?void 0:t.split(" ");null!=s&&(e=!0,a.splice(0,1,...s));for(let t=0;t{t.delay=null,n()},e),t.delayResolve=n})}function s(e,t,n=null){var a="";null!=n&&(a=" (Line "+function(e,t){var n=t.scriptRef.codeCode();try{return((n=n.substring(0,e.start)).match(/\n/g)||[]).length+1}catch(e){return-1}}(n,e)+")");const r=i.AllServers[e.serverIp];if(null==r)throw new Error(`WorkerScript constructed with invalid server ip: ${this.serverIp}`);return"|"+r.hostname+"|"+e.name+"|"+t+a}function l(e,t,n){const a=e.scriptRef.threads;if(!n)return isNaN(a)||a<1?1:a;const r=0|n;if(isNaN(n)||r<1)throw s(e,`Invalid thread count passed to ${t}: ${n}. Threads must be a positive number.`);if(n>a)throw s(e,`Too many threads requested by ${t}. Requested: ${n}. Has: ${a}.`);return r}function c(e){if(!Object(r.isString)(e))return!1;return 4==e.split("|").length}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.setTimeoutRef=void 0,t.setTimeoutRef=window.setTimeout.bind(window)},,function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.killWorkerScript=void 0;const a=n(258),r=n(123),i=n(159),o=n(205),s=n(27),l=n(206),c=n(301);function u(e,t=!0){const n=r.workerScripts.get(e);return n instanceof a.WorkerScript&&(m(n,t),!0)}function m(e,t=!0){e.env.stopFlag=!0,function(e){e instanceof a.WorkerScript&&e.delay&&(clearTimeout(e.delay),e.delayResolve&&e.delayResolve())}(e),function(e,t=!0){if(!(e instanceof a.WorkerScript))return console.error("Invalid argument passed into removeWorkerScript():"),void console.error(e);{const n=e.serverIp,a=e.name,o=s.AllServers[n];if(null==o)return void console.error(`Could not find server on which this script is running: ${n}`);o.ramUsed=c.roundToTwo(o.ramUsed-e.ramUsage),o.ramUsed<0&&(console.warn(`Server (${o.hostname}) RAM usage went negative (if it's due to floating pt imprecision, it's okay): ${o.ramUsed}`),o.ramUsed=0);for(let t=0;t["+(_Fconf_FconfSettings__WEBPACK_IMPORTED_MODULE_12__.FconfSettings.ENABLE_TIMESTAMPS?Object(_utils_helpers_getTimestamp__WEBPACK_IMPORTED_MODULE_37__.getTimestamp)()+" ":"")+_Player__WEBPACK_IMPORTED_MODULE_22__.Player.getCurrentServer().hostname+` ~${n}]> ${t}`),t.length>0&&(Terminal.resetTerminalInput(),Terminal.executeCommands(t))}if(e.keyCode===_utils_helpers_keyCodes__WEBPACK_IMPORTED_MODULE_35__.KEY.C&&e.ctrlKey&&(_engine__WEBPACK_IMPORTED_MODULE_11__.Engine._actionInProgress?(Object(_ui_postToTerminal__WEBPACK_IMPORTED_MODULE_40__.post)("Cancelling..."),_engine__WEBPACK_IMPORTED_MODULE_11__.Engine._actionInProgress=!1,Terminal.finishAction(!0)):_Fconf_FconfSettings__WEBPACK_IMPORTED_MODULE_12__.FconfSettings.ENABLE_BASH_HOTKEYS&&Terminal.resetTerminalInput()),e.keyCode===_utils_helpers_keyCodes__WEBPACK_IMPORTED_MODULE_35__.KEY.L&&e.ctrlKey&&(e.preventDefault(),Terminal.executeCommand("clear")),e.keyCode===_utils_helpers_keyCodes__WEBPACK_IMPORTED_MODULE_35__.KEY.UPARROW||_Fconf_FconfSettings__WEBPACK_IMPORTED_MODULE_12__.FconfSettings.ENABLE_BASH_HOTKEYS&&e.keyCode===_utils_helpers_keyCodes__WEBPACK_IMPORTED_MODULE_35__.KEY.P&&e.ctrlKey){if(_Fconf_FconfSettings__WEBPACK_IMPORTED_MODULE_12__.FconfSettings.ENABLE_BASH_HOTKEYS&&e.preventDefault(),null==t)return;var n=Terminal.commandHistoryIndex;if(0==(r=Terminal.commandHistory.length))return;(n<0||n>r)&&(Terminal.commandHistoryIndex=r),0!=n&&--Terminal.commandHistoryIndex;var a=Terminal.commandHistory[Terminal.commandHistoryIndex];t.value=a,Object(_utils_SetTimeoutRef__WEBPACK_IMPORTED_MODULE_32__.setTimeoutRef)(function(){t.selectionStart=t.selectionEnd=1e4},10)}if(e.keyCode===_utils_helpers_keyCodes__WEBPACK_IMPORTED_MODULE_35__.KEY.DOWNARROW||_Fconf_FconfSettings__WEBPACK_IMPORTED_MODULE_12__.FconfSettings.ENABLE_BASH_HOTKEYS&&e.keyCode===_utils_helpers_keyCodes__WEBPACK_IMPORTED_MODULE_35__.KEY.M&&e.ctrlKey){if(_Fconf_FconfSettings__WEBPACK_IMPORTED_MODULE_12__.FconfSettings.ENABLE_BASH_HOTKEYS&&e.preventDefault(),null==t)return;var r;n=Terminal.commandHistoryIndex;if(0==(r=Terminal.commandHistory.length))return;if((n<0||n>r)&&(Terminal.commandHistoryIndex=r),n==r||n==r-1)Terminal.commandHistoryIndex=r,t.value="";else{++Terminal.commandHistoryIndex;a=Terminal.commandHistory[Terminal.commandHistoryIndex];t.value=a}}if(e.keyCode===_utils_helpers_keyCodes__WEBPACK_IMPORTED_MODULE_35__.KEY.TAB){if(e.preventDefault(),null==t)return;let n=t.value;if(""==n)return;const a=n.lastIndexOf(";");-1!==a&&(n=n.slice(a+1));const r=(n=(n=n.trim()).replace(/\s\s+/g," ")).split(" ");let i=r.length-2;i<-1&&(i=0);const o=Object(_Terminal_determineAllPossibilitiesForTabCompletion__WEBPACK_IMPORTED_MODULE_1__.determineAllPossibilitiesForTabCompletion)(_Player__WEBPACK_IMPORTED_MODULE_22__.Player,n,i,Terminal.currDir);if(0==o.length)return;let s="",l="";if(0==r.length)return;1==r.length?l=r[0]:2==r.length?(l=r[0],s=r[1]):3==r.length?(l=r[0]+" "+r[1],s=r[2]):(s=r.pop(),l=r.join(" ")),Object(_Terminal_tabCompletion__WEBPACK_IMPORTED_MODULE_3__.tabCompletion)(l,s,o),t.focus()}_Fconf_FconfSettings__WEBPACK_IMPORTED_MODULE_12__.FconfSettings.ENABLE_BASH_HOTKEYS&&(e.keyCode===_utils_helpers_keyCodes__WEBPACK_IMPORTED_MODULE_35__.KEY.A&&e.ctrlKey&&(e.preventDefault(),Terminal.moveTextCursor("home")),e.keyCode===_utils_helpers_keyCodes__WEBPACK_IMPORTED_MODULE_35__.KEY.E&&e.ctrlKey&&(e.preventDefault(),Terminal.moveTextCursor("end")),e.keyCode===_utils_helpers_keyCodes__WEBPACK_IMPORTED_MODULE_35__.KEY.B&&e.ctrlKey&&(e.preventDefault(),Terminal.moveTextCursor("prevchar")),e.keyCode===_utils_helpers_keyCodes__WEBPACK_IMPORTED_MODULE_35__.KEY.B&&e.altKey&&(e.preventDefault(),Terminal.moveTextCursor("prevword")),e.keyCode===_utils_helpers_keyCodes__WEBPACK_IMPORTED_MODULE_35__.KEY.F&&e.ctrlKey&&(e.preventDefault(),Terminal.moveTextCursor("nextchar")),e.keyCode===_utils_helpers_keyCodes__WEBPACK_IMPORTED_MODULE_35__.KEY.F&&e.altKey&&(e.preventDefault(),Terminal.moveTextCursor("nextword")),e.keyCode!==_utils_helpers_keyCodes__WEBPACK_IMPORTED_MODULE_35__.KEY.H&&e.keyCode!==_utils_helpers_keyCodes__WEBPACK_IMPORTED_MODULE_35__.KEY.D||!e.ctrlKey||(Terminal.modifyInput("backspace"),e.preventDefault()))}});let terminalCtrlPressed=!1,shiftKeyPressed=!1;$(document).ready(function(){_ui_navigationTracking__WEBPACK_IMPORTED_MODULE_33__.routing.isOn(_ui_navigationTracking__WEBPACK_IMPORTED_MODULE_33__.Page.Terminal)&&$(".terminal-input").focus()}),$(document).keydown(function(e){if(_ui_navigationTracking__WEBPACK_IMPORTED_MODULE_33__.routing.isOn(_ui_navigationTracking__WEBPACK_IMPORTED_MODULE_33__.Page.Terminal))if(e.which==_utils_helpers_keyCodes__WEBPACK_IMPORTED_MODULE_35__.KEY.CTRL)terminalCtrlPressed=!0;else if(e.shiftKey)shiftKeyPressed=!0;else if(terminalCtrlPressed||shiftKeyPressed||Terminal.contractOpen);else{var t=document.getElementById("terminal-input-text-box");null!=t&&t.focus(),terminalCtrlPressed=!1,shiftKeyPressed=!1}}),$(document).keyup(function(e){_ui_navigationTracking__WEBPACK_IMPORTED_MODULE_33__.routing.isOn(_ui_navigationTracking__WEBPACK_IMPORTED_MODULE_33__.Page.Terminal)&&(e.which==_utils_helpers_keyCodes__WEBPACK_IMPORTED_MODULE_35__.KEY.CTRL&&(terminalCtrlPressed=!1),e.shiftKey&&(shiftKeyPressed=!1))});let Terminal={hackFlag:!1,backdoorFlag:!1,analyzeFlag:!1,actionStarted:!1,actionTime:0,commandHistory:[],commandHistoryIndex:0,contractOpen:!1,currDir:"/",resetTerminalInput:function(e=!1){let t="";e&&(t=getTerminalInput());const n=Terminal.currDir;_Fconf_FconfSettings__WEBPACK_IMPORTED_MODULE_12__.FconfSettings.WRAP_INPUT?(document.getElementById("terminal-input-td").innerHTML=`
        [${_Player__WEBPACK_IMPORTED_MODULE_22__.Player.getCurrentServer().hostname} ~${n}]$
        `+`",m.noCloneChecked=!!e.cloneNode(!0).lastChild.defaultValue,e.innerHTML="",m.option=!!e.lastChild}();var me={thead:[1,"","
        "],col:[2,"","
        "],tr:[2,"","
        "],td:[3,"","
        "],_default:[0,"",""]};function ve(e,t){var n;return n=void 0!==e.getElementsByTagName?e.getElementsByTagName(t||"*"):void 0!==e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&D(e,t)?k.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;n",""]);var be=/<|&#?\w+;/;function xe(e,t,n,r,i){for(var o,a,s,l,u,c,h=t.createDocumentFragment(),d=[],f=0,p=e.length;f-1)i&&i.push(o);else if(u=ae(o),a=ve(h.appendChild(o),"script"),u&&ye(a),n)for(c=0;o=a[c++];)ge.test(o.type||"")&&n.push(o);return h}var we=/^key/,Ce=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,ke=/^([^.]*)(?:\.(.+)|)/;function Ee(){return!0}function Ae(){return!1}function _e(e,t){return e===function(){try{return b.activeElement}catch(e){}}()==("focus"===t)}function Se(e,t,n,r,i,o){var a,s;if("object"==typeof t){for(s in"string"!=typeof n&&(r=r||n,n=void 0),t)Se(e,s,n,r,t[s],o);return e}if(null==r&&null==i?(i=n,r=n=void 0):null==i&&("string"==typeof n?(i=r,r=void 0):(i=r,r=n,n=void 0)),!1===i)i=Ae;else if(!i)return e;return 1===o&&(a=i,(i=function(e){return k().off(e),a.apply(this,arguments)}).guid=a.guid||(a.guid=k.guid++)),e.each(function(){k.event.add(this,t,i,r,n)})}function Fe(e,t,n){n?(Z.set(e,t,!1),k.event.add(e,t,{namespace:!1,handler:function(e){var r,i,o=Z.get(this,t);if(1&e.isTrigger&&this[t]){if(o.length)(k.event.special[t]||{}).delegateType&&e.stopPropagation();else if(o=s.call(arguments),Z.set(this,t,o),r=n(this,t),this[t](),o!==(i=Z.get(this,t))||r?Z.set(this,t,!1):i={},o!==i)return e.stopImmediatePropagation(),e.preventDefault(),i.value}else o.length&&(Z.set(this,t,{value:k.event.trigger(k.extend(o[0],k.Event.prototype),o.slice(1),this)}),e.stopImmediatePropagation())}})):void 0===Z.get(e,t)&&k.event.add(e,t,Ee)}k.event={global:{},add:function(e,t,n,r,i){var o,a,s,l,u,c,h,d,f,p,g,m=Z.get(e);if(Y(e))for(n.handler&&(n=(o=n).handler,i=o.selector),i&&k.find.matchesSelector(oe,i),n.guid||(n.guid=k.guid++),(l=m.events)||(l=m.events=Object.create(null)),(a=m.handle)||(a=m.handle=function(t){return void 0!==k&&k.event.triggered!==t.type?k.event.dispatch.apply(e,arguments):void 0}),u=(t=(t||"").match(P)||[""]).length;u--;)f=g=(s=ke.exec(t[u])||[])[1],p=(s[2]||"").split(".").sort(),f&&(h=k.event.special[f]||{},f=(i?h.delegateType:h.bindType)||f,h=k.event.special[f]||{},c=k.extend({type:f,origType:g,data:r,handler:n,guid:n.guid,selector:i,needsContext:i&&k.expr.match.needsContext.test(i),namespace:p.join(".")},o),(d=l[f])||((d=l[f]=[]).delegateCount=0,h.setup&&!1!==h.setup.call(e,r,p,a)||e.addEventListener&&e.addEventListener(f,a)),h.add&&(h.add.call(e,c),c.handler.guid||(c.handler.guid=n.guid)),i?d.splice(d.delegateCount++,0,c):d.push(c),k.event.global[f]=!0)},remove:function(e,t,n,r,i){var o,a,s,l,u,c,h,d,f,p,g,m=Z.hasData(e)&&Z.get(e);if(m&&(l=m.events)){for(u=(t=(t||"").match(P)||[""]).length;u--;)if(f=g=(s=ke.exec(t[u])||[])[1],p=(s[2]||"").split(".").sort(),f){for(h=k.event.special[f]||{},d=l[f=(r?h.delegateType:h.bindType)||f]||[],s=s[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),a=o=d.length;o--;)c=d[o],!i&&g!==c.origType||n&&n.guid!==c.guid||s&&!s.test(c.namespace)||r&&r!==c.selector&&("**"!==r||!c.selector)||(d.splice(o,1),c.selector&&d.delegateCount--,h.remove&&h.remove.call(e,c));a&&!d.length&&(h.teardown&&!1!==h.teardown.call(e,p,m.handle)||k.removeEvent(e,f,m.handle),delete l[f])}else for(f in l)k.event.remove(e,f+t[u],n,r,!0);k.isEmptyObject(l)&&Z.remove(e,"handle events")}},dispatch:function(e){var t,n,r,i,o,a,s=new Array(arguments.length),l=k.event.fix(e),u=(Z.get(this,"events")||Object.create(null))[l.type]||[],c=k.event.special[l.type]||{};for(s[0]=l,t=1;t=1))for(;u!==this;u=u.parentNode||this)if(1===u.nodeType&&("click"!==e.type||!0!==u.disabled)){for(o=[],a={},n=0;n-1:k.find(i,this,null,[u]).length),a[i]&&o.push(r);o.length&&s.push({elem:u,handlers:o})}return u=this,l\s*$/g;function Le(e,t){return D(e,"table")&&D(11!==t.nodeType?t:t.firstChild,"tr")&&k(e).children("tbody")[0]||e}function Be(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function Me(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function je(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(Z.hasData(e)&&(s=Z.get(e).events))for(i in Z.remove(t,"handle events"),s)for(n=0,r=s[i].length;n1&&"string"==typeof p&&!m.checkClone&&Te.test(p))return e.each(function(i){var o=e.eq(i);g&&(t[0]=p.call(this,i,o.html())),Pe(o,t,n,r)});if(d&&(o=(i=xe(t,e[0].ownerDocument,!1,e,r)).firstChild,1===i.childNodes.length&&(i=o),o||r)){for(s=(a=k.map(ve(i,"script"),Be)).length;h0&&ye(a,!l&&ve(e,"script")),s},cleanData:function(e){for(var t,n,r,i=k.event.special,o=0;void 0!==(n=e[o]);o++)if(Y(n)){if(t=n[Z.expando]){if(t.events)for(r in t.events)i[r]?k.event.remove(n,r):k.removeEvent(n,r,t.handle);n[Z.expando]=void 0}n[Q.expando]&&(n[Q.expando]=void 0)}}}),k.fn.extend({detach:function(e){return Ie(this,e,!0)},remove:function(e){return Ie(this,e)},text:function(e){return V(this,function(e){return void 0===e?k.text(this):this.empty().each(function(){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||(this.textContent=e)})},null,e,arguments.length)},append:function(){return Pe(this,arguments,function(e){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||Le(this,e).appendChild(e)})},prepend:function(){return Pe(this,arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=Le(this,e);t.insertBefore(e,t.firstChild)}})},before:function(){return Pe(this,arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this)})},after:function(){return Pe(this,arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this.nextSibling)})},empty:function(){for(var e,t=0;null!=(e=this[t]);t++)1===e.nodeType&&(k.cleanData(ve(e,!1)),e.textContent="");return this},clone:function(e,t){return e=null!=e&&e,t=null==t?e:t,this.map(function(){return k.clone(this,e,t)})},html:function(e){return V(this,function(e){var t=this[0]||{},n=0,r=this.length;if(void 0===e&&1===t.nodeType)return t.innerHTML;if("string"==typeof e&&!De.test(e)&&!me[(pe.exec(e)||["",""])[1].toLowerCase()]){e=k.htmlPrefilter(e);try{for(;n3,oe.removeChild(e)),s}}))}();var Ue=["Webkit","Moz","ms"],Ge=b.createElement("div").style,Ke={};function qe(e){var t=k.cssProps[e]||Ke[e];return t||(e in Ge?e:Ke[e]=function(e){for(var t=e[0].toUpperCase()+e.slice(1),n=Ue.length;n--;)if((e=Ue[n]+t)in Ge)return e}(e)||e)}var Ye=/^(none|table(?!-c[ea]).+)/,Xe=/^--/,Ze={position:"absolute",visibility:"hidden",display:"block"},Qe={letterSpacing:"0",fontWeight:"400"};function Je(e,t,n){var r=re.exec(t);return r?Math.max(0,r[2]-(n||0))+(r[3]||"px"):t}function et(e,t,n,r,i,o){var a="width"===t?1:0,s=0,l=0;if(n===(r?"border":"content"))return 0;for(;a<4;a+=2)"margin"===n&&(l+=k.css(e,n+ie[a],!0,i)),r?("content"===n&&(l-=k.css(e,"padding"+ie[a],!0,i)),"margin"!==n&&(l-=k.css(e,"border"+ie[a]+"Width",!0,i))):(l+=k.css(e,"padding"+ie[a],!0,i),"padding"!==n?l+=k.css(e,"border"+ie[a]+"Width",!0,i):s+=k.css(e,"border"+ie[a]+"Width",!0,i));return!r&&o>=0&&(l+=Math.max(0,Math.ceil(e["offset"+t[0].toUpperCase()+t.slice(1)]-o-l-s-.5))||0),l}function tt(e,t,n){var r=$e(e),i=(!m.boxSizingReliable()||n)&&"border-box"===k.css(e,"boxSizing",!1,r),o=i,a=He(e,t,r),s="offset"+t[0].toUpperCase()+t.slice(1);if(Ne.test(a)){if(!n)return a;a="auto"}return(!m.boxSizingReliable()&&i||!m.reliableTrDimensions()&&D(e,"tr")||"auto"===a||!parseFloat(a)&&"inline"===k.css(e,"display",!1,r))&&e.getClientRects().length&&(i="border-box"===k.css(e,"boxSizing",!1,r),(o=s in e)&&(a=e[s])),(a=parseFloat(a)||0)+et(e,t,n||(i?"border":"content"),o,r,a)+"px"}function nt(e,t,n,r,i){return new nt.prototype.init(e,t,n,r,i)}k.extend({cssHooks:{opacity:{get:function(e,t){if(t){var n=He(e,"opacity");return""===n?"1":n}}}},cssNumber:{animationIterationCount:!0,columnCount:!0,fillOpacity:!0,flexGrow:!0,flexShrink:!0,fontWeight:!0,gridArea:!0,gridColumn:!0,gridColumnEnd:!0,gridColumnStart:!0,gridRow:!0,gridRowEnd:!0,gridRowStart:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{},style:function(e,t,n,r){if(e&&3!==e.nodeType&&8!==e.nodeType&&e.style){var i,o,a,s=q(t),l=Xe.test(t),u=e.style;if(l||(t=qe(s)),a=k.cssHooks[t]||k.cssHooks[s],void 0===n)return a&&"get"in a&&void 0!==(i=a.get(e,!1,r))?i:u[t];"string"===(o=typeof n)&&(i=re.exec(n))&&i[1]&&(n=ue(e,t,i),o="number"),null!=n&&n==n&&("number"!==o||l||(n+=i&&i[3]||(k.cssNumber[s]?"":"px")),m.clearCloneStyle||""!==n||0!==t.indexOf("background")||(u[t]="inherit"),a&&"set"in a&&void 0===(n=a.set(e,n,r))||(l?u.setProperty(t,n):u[t]=n))}},css:function(e,t,n,r){var i,o,a,s=q(t);return Xe.test(t)||(t=qe(s)),(a=k.cssHooks[t]||k.cssHooks[s])&&"get"in a&&(i=a.get(e,!0,n)),void 0===i&&(i=He(e,t,r)),"normal"===i&&t in Qe&&(i=Qe[t]),""===n||n?(o=parseFloat(i),!0===n||isFinite(o)?o||0:i):i}}),k.each(["height","width"],function(e,t){k.cssHooks[t]={get:function(e,n,r){if(n)return!Ye.test(k.css(e,"display"))||e.getClientRects().length&&e.getBoundingClientRect().width?tt(e,t,r):We(e,Ze,function(){return tt(e,t,r)})},set:function(e,n,r){var i,o=$e(e),a=!m.scrollboxSize()&&"absolute"===o.position,s=(a||r)&&"border-box"===k.css(e,"boxSizing",!1,o),l=r?et(e,t,r,s,o):0;return s&&a&&(l-=Math.ceil(e["offset"+t[0].toUpperCase()+t.slice(1)]-parseFloat(o[t])-et(e,t,"border",!1,o)-.5)),l&&(i=re.exec(n))&&"px"!==(i[3]||"px")&&(e.style[t]=n,n=k.css(e,t)),Je(0,n,l)}}}),k.cssHooks.marginLeft=Ve(m.reliableMarginLeft,function(e,t){if(t)return(parseFloat(He(e,"marginLeft"))||e.getBoundingClientRect().left-We(e,{marginLeft:0},function(){return e.getBoundingClientRect().left}))+"px"}),k.each({margin:"",padding:"",border:"Width"},function(e,t){k.cssHooks[e+t]={expand:function(n){for(var r=0,i={},o="string"==typeof n?n.split(" "):[n];r<4;r++)i[e+ie[r]+t]=o[r]||o[r-2]||o[0];return i}},"margin"!==e&&(k.cssHooks[e+t].set=Je)}),k.fn.extend({css:function(e,t){return V(this,function(e,t,n){var r,i,o={},a=0;if(Array.isArray(t)){for(r=$e(e),i=t.length;a1)}}),k.Tween=nt,nt.prototype={constructor:nt,init:function(e,t,n,r,i,o){this.elem=e,this.prop=n,this.easing=i||k.easing._default,this.options=t,this.start=this.now=this.cur(),this.end=r,this.unit=o||(k.cssNumber[n]?"":"px")},cur:function(){var e=nt.propHooks[this.prop];return e&&e.get?e.get(this):nt.propHooks._default.get(this)},run:function(e){var t,n=nt.propHooks[this.prop];return this.options.duration?this.pos=t=k.easing[this.easing](e,this.options.duration*e,0,1,this.options.duration):this.pos=t=e,this.now=(this.end-this.start)*t+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),n&&n.set?n.set(this):nt.propHooks._default.set(this),this}},nt.prototype.init.prototype=nt.prototype,nt.propHooks={_default:{get:function(e){var t;return 1!==e.elem.nodeType||null!=e.elem[e.prop]&&null==e.elem.style[e.prop]?e.elem[e.prop]:(t=k.css(e.elem,e.prop,""))&&"auto"!==t?t:0},set:function(e){k.fx.step[e.prop]?k.fx.step[e.prop](e):1!==e.elem.nodeType||!k.cssHooks[e.prop]&&null==e.elem.style[qe(e.prop)]?e.elem[e.prop]=e.now:k.style(e.elem,e.prop,e.now+e.unit)}}},nt.propHooks.scrollTop=nt.propHooks.scrollLeft={set:function(e){e.elem.nodeType&&e.elem.parentNode&&(e.elem[e.prop]=e.now)}},k.easing={linear:function(e){return e},swing:function(e){return.5-Math.cos(e*Math.PI)/2},_default:"swing"},k.fx=nt.prototype.init,k.fx.step={};var rt,it,ot=/^(?:toggle|show|hide)$/,at=/queueHooks$/;function st(){it&&(!1===b.hidden&&n.requestAnimationFrame?n.requestAnimationFrame(st):n.setTimeout(st,k.fx.interval),k.fx.tick())}function lt(){return n.setTimeout(function(){rt=void 0}),rt=Date.now()}function ut(e,t){var n,r=0,i={height:e};for(t=t?1:0;r<4;r+=2-t)i["margin"+(n=ie[r])]=i["padding"+n]=e;return t&&(i.opacity=i.width=e),i}function ct(e,t,n){for(var r,i=(ht.tweeners[t]||[]).concat(ht.tweeners["*"]),o=0,a=i.length;o1)},removeAttr:function(e){return this.each(function(){k.removeAttr(this,e)})}}),k.extend({attr:function(e,t,n){var r,i,o=e.nodeType;if(3!==o&&8!==o&&2!==o)return void 0===e.getAttribute?k.prop(e,t,n):(1===o&&k.isXMLDoc(e)||(i=k.attrHooks[t.toLowerCase()]||(k.expr.match.bool.test(t)?dt:void 0)),void 0!==n?null===n?void k.removeAttr(e,t):i&&"set"in i&&void 0!==(r=i.set(e,n,t))?r:(e.setAttribute(t,n+""),n):i&&"get"in i&&null!==(r=i.get(e,t))?r:null==(r=k.find.attr(e,t))?void 0:r)},attrHooks:{type:{set:function(e,t){if(!m.radioValue&&"radio"===t&&D(e,"input")){var n=e.value;return e.setAttribute("type",t),n&&(e.value=n),t}}}},removeAttr:function(e,t){var n,r=0,i=t&&t.match(P);if(i&&1===e.nodeType)for(;n=i[r++];)e.removeAttribute(n)}}),dt={set:function(e,t,n){return!1===t?k.removeAttr(e,n):e.setAttribute(n,n),n}},k.each(k.expr.match.bool.source.match(/\w+/g),function(e,t){var n=ft[t]||k.find.attr;ft[t]=function(e,t,r){var i,o,a=t.toLowerCase();return r||(o=ft[a],ft[a]=i,i=null!=n(e,t,r)?a:null,ft[a]=o),i}});var pt=/^(?:input|select|textarea|button)$/i,gt=/^(?:a|area)$/i;function mt(e){return(e.match(P)||[]).join(" ")}function vt(e){return e.getAttribute&&e.getAttribute("class")||""}function yt(e){return Array.isArray(e)?e:"string"==typeof e&&e.match(P)||[]}k.fn.extend({prop:function(e,t){return V(this,k.prop,e,t,arguments.length>1)},removeProp:function(e){return this.each(function(){delete this[k.propFix[e]||e]})}}),k.extend({prop:function(e,t,n){var r,i,o=e.nodeType;if(3!==o&&8!==o&&2!==o)return 1===o&&k.isXMLDoc(e)||(t=k.propFix[t]||t,i=k.propHooks[t]),void 0!==n?i&&"set"in i&&void 0!==(r=i.set(e,n,t))?r:e[t]=n:i&&"get"in i&&null!==(r=i.get(e,t))?r:e[t]},propHooks:{tabIndex:{get:function(e){var t=k.find.attr(e,"tabindex");return t?parseInt(t,10):pt.test(e.nodeName)||gt.test(e.nodeName)&&e.href?0:-1}}},propFix:{for:"htmlFor",class:"className"}}),m.optSelected||(k.propHooks.selected={get:function(e){var t=e.parentNode;return t&&t.parentNode&&t.parentNode.selectedIndex,null},set:function(e){var t=e.parentNode;t&&(t.selectedIndex,t.parentNode&&t.parentNode.selectedIndex)}}),k.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){k.propFix[this.toLowerCase()]=this}),k.fn.extend({addClass:function(e){var t,n,r,i,o,a,s,l=0;if(v(e))return this.each(function(t){k(this).addClass(e.call(this,t,vt(this)))});if((t=yt(e)).length)for(;n=this[l++];)if(i=vt(n),r=1===n.nodeType&&" "+mt(i)+" "){for(a=0;o=t[a++];)r.indexOf(" "+o+" ")<0&&(r+=o+" ");i!==(s=mt(r))&&n.setAttribute("class",s)}return this},removeClass:function(e){var t,n,r,i,o,a,s,l=0;if(v(e))return this.each(function(t){k(this).removeClass(e.call(this,t,vt(this)))});if(!arguments.length)return this.attr("class","");if((t=yt(e)).length)for(;n=this[l++];)if(i=vt(n),r=1===n.nodeType&&" "+mt(i)+" "){for(a=0;o=t[a++];)for(;r.indexOf(" "+o+" ")>-1;)r=r.replace(" "+o+" "," ");i!==(s=mt(r))&&n.setAttribute("class",s)}return this},toggleClass:function(e,t){var n=typeof e,r="string"===n||Array.isArray(e);return"boolean"==typeof t&&r?t?this.addClass(e):this.removeClass(e):v(e)?this.each(function(n){k(this).toggleClass(e.call(this,n,vt(this),t),t)}):this.each(function(){var t,i,o,a;if(r)for(i=0,o=k(this),a=yt(e);t=a[i++];)o.hasClass(t)?o.removeClass(t):o.addClass(t);else void 0!==e&&"boolean"!==n||((t=vt(this))&&Z.set(this,"__className__",t),this.setAttribute&&this.setAttribute("class",t||!1===e?"":Z.get(this,"__className__")||""))})},hasClass:function(e){var t,n,r=0;for(t=" "+e+" ";n=this[r++];)if(1===n.nodeType&&(" "+mt(vt(n))+" ").indexOf(t)>-1)return!0;return!1}});var bt=/\r/g;k.fn.extend({val:function(e){var t,n,r,i=this[0];return arguments.length?(r=v(e),this.each(function(n){var i;1===this.nodeType&&(null==(i=r?e.call(this,n,k(this).val()):e)?i="":"number"==typeof i?i+="":Array.isArray(i)&&(i=k.map(i,function(e){return null==e?"":e+""})),(t=k.valHooks[this.type]||k.valHooks[this.nodeName.toLowerCase()])&&"set"in t&&void 0!==t.set(this,i,"value")||(this.value=i))})):i?(t=k.valHooks[i.type]||k.valHooks[i.nodeName.toLowerCase()])&&"get"in t&&void 0!==(n=t.get(i,"value"))?n:"string"==typeof(n=i.value)?n.replace(bt,""):null==n?"":n:void 0}}),k.extend({valHooks:{option:{get:function(e){var t=k.find.attr(e,"value");return null!=t?t:mt(k.text(e))}},select:{get:function(e){var t,n,r,i=e.options,o=e.selectedIndex,a="select-one"===e.type,s=a?null:[],l=a?o+1:i.length;for(r=o<0?l:a?o:0;r-1)&&(n=!0);return n||(e.selectedIndex=-1),o}}}}),k.each(["radio","checkbox"],function(){k.valHooks[this]={set:function(e,t){if(Array.isArray(t))return e.checked=k.inArray(k(e).val(),t)>-1}},m.checkOn||(k.valHooks[this].get=function(e){return null===e.getAttribute("value")?"on":e.value})}),m.focusin="onfocusin"in n;var xt=/^(?:focusinfocus|focusoutblur)$/,wt=function(e){e.stopPropagation()};k.extend(k.event,{trigger:function(e,t,r,i){var o,a,s,l,u,c,h,d,p=[r||b],g=f.call(e,"type")?e.type:e,m=f.call(e,"namespace")?e.namespace.split("."):[];if(a=d=s=r=r||b,3!==r.nodeType&&8!==r.nodeType&&!xt.test(g+k.event.triggered)&&(g.indexOf(".")>-1&&(g=(m=g.split(".")).shift(),m.sort()),u=g.indexOf(":")<0&&"on"+g,(e=e[k.expando]?e:new k.Event(g,"object"==typeof e&&e)).isTrigger=i?2:3,e.namespace=m.join("."),e.rnamespace=e.namespace?new RegExp("(^|\\.)"+m.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,e.result=void 0,e.target||(e.target=r),t=null==t?[e]:k.makeArray(t,[e]),h=k.event.special[g]||{},i||!h.trigger||!1!==h.trigger.apply(r,t))){if(!i&&!h.noBubble&&!y(r)){for(l=h.delegateType||g,xt.test(l+g)||(a=a.parentNode);a;a=a.parentNode)p.push(a),s=a;s===(r.ownerDocument||b)&&p.push(s.defaultView||s.parentWindow||n)}for(o=0;(a=p[o++])&&!e.isPropagationStopped();)d=a,e.type=o>1?l:h.bindType||g,(c=(Z.get(a,"events")||Object.create(null))[e.type]&&Z.get(a,"handle"))&&c.apply(a,t),(c=u&&a[u])&&c.apply&&Y(a)&&(e.result=c.apply(a,t),!1===e.result&&e.preventDefault());return e.type=g,i||e.isDefaultPrevented()||h._default&&!1!==h._default.apply(p.pop(),t)||!Y(r)||u&&v(r[g])&&!y(r)&&((s=r[u])&&(r[u]=null),k.event.triggered=g,e.isPropagationStopped()&&d.addEventListener(g,wt),r[g](),e.isPropagationStopped()&&d.removeEventListener(g,wt),k.event.triggered=void 0,s&&(r[u]=s)),e.result}},simulate:function(e,t,n){var r=k.extend(new k.Event,n,{type:e,isSimulated:!0});k.event.trigger(r,null,t)}}),k.fn.extend({trigger:function(e,t){return this.each(function(){k.event.trigger(e,t,this)})},triggerHandler:function(e,t){var n=this[0];if(n)return k.event.trigger(e,t,n,!0)}}),m.focusin||k.each({focus:"focusin",blur:"focusout"},function(e,t){var n=function(e){k.event.simulate(t,e.target,k.event.fix(e))};k.event.special[t]={setup:function(){var r=this.ownerDocument||this.document||this,i=Z.access(r,t);i||r.addEventListener(e,n,!0),Z.access(r,t,(i||0)+1)},teardown:function(){var r=this.ownerDocument||this.document||this,i=Z.access(r,t)-1;i?Z.access(r,t,i):(r.removeEventListener(e,n,!0),Z.remove(r,t))}}});var Ct=n.location,kt={guid:Date.now()},Et=/\?/;k.parseXML=function(e){var t;if(!e||"string"!=typeof e)return null;try{t=(new n.DOMParser).parseFromString(e,"text/xml")}catch(e){t=void 0}return t&&!t.getElementsByTagName("parsererror").length||k.error("Invalid XML: "+e),t};var At=/\[\]$/,_t=/\r?\n/g,St=/^(?:submit|button|image|reset|file)$/i,Ft=/^(?:input|select|textarea|keygen)/i;function Dt(e,t,n,r){var i;if(Array.isArray(t))k.each(t,function(t,i){n||At.test(e)?r(e,i):Dt(e+"["+("object"==typeof i&&null!=i?t:"")+"]",i,n,r)});else if(n||"object"!==C(t))r(e,t);else for(i in t)Dt(e+"["+i+"]",t[i],n,r)}k.param=function(e,t){var n,r=[],i=function(e,t){var n=v(t)?t():t;r[r.length]=encodeURIComponent(e)+"="+encodeURIComponent(null==n?"":n)};if(null==e)return"";if(Array.isArray(e)||e.jquery&&!k.isPlainObject(e))k.each(e,function(){i(this.name,this.value)});else for(n in e)Dt(n,e[n],t,i);return r.join("&")},k.fn.extend({serialize:function(){return k.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var e=k.prop(this,"elements");return e?k.makeArray(e):this}).filter(function(){var e=this.type;return this.name&&!k(this).is(":disabled")&&Ft.test(this.nodeName)&&!St.test(e)&&(this.checked||!fe.test(e))}).map(function(e,t){var n=k(this).val();return null==n?null:Array.isArray(n)?k.map(n,function(e){return{name:t.name,value:e.replace(_t,"\r\n")}}):{name:t.name,value:n.replace(_t,"\r\n")}}).get()}});var Tt=/%20/g,Ot=/#.*$/,Lt=/([?&])_=[^&]*/,Bt=/^(.*?):[ \t]*([^\r\n]*)$/gm,Mt=/^(?:GET|HEAD)$/,jt=/^\/\//,Rt={},Pt={},It="*/".concat("*"),Nt=b.createElement("a");function $t(e){return function(t,n){"string"!=typeof t&&(n=t,t="*");var r,i=0,o=t.toLowerCase().match(P)||[];if(v(n))for(;r=o[i++];)"+"===r[0]?(r=r.slice(1)||"*",(e[r]=e[r]||[]).unshift(n)):(e[r]=e[r]||[]).push(n)}}function Wt(e,t,n,r){var i={},o=e===Pt;function a(s){var l;return i[s]=!0,k.each(e[s]||[],function(e,s){var u=s(t,n,r);return"string"!=typeof u||o||i[u]?o?!(l=u):void 0:(t.dataTypes.unshift(u),a(u),!1)}),l}return a(t.dataTypes[0])||!i["*"]&&a("*")}function zt(e,t){var n,r,i=k.ajaxSettings.flatOptions||{};for(n in t)void 0!==t[n]&&((i[n]?e:r||(r={}))[n]=t[n]);return r&&k.extend(!0,e,r),e}Nt.href=Ct.href,k.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:Ct.href,type:"GET",isLocal:/^(?:about|app|app-storage|.+-extension|file|res|widget):$/.test(Ct.protocol),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":It,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/\bxml\b/,html:/\bhtml/,json:/\bjson\b/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":JSON.parse,"text xml":k.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(e,t){return t?zt(zt(e,k.ajaxSettings),t):zt(k.ajaxSettings,e)},ajaxPrefilter:$t(Rt),ajaxTransport:$t(Pt),ajax:function(e,t){"object"==typeof e&&(t=e,e=void 0),t=t||{};var r,i,o,a,s,l,u,c,h,d,f=k.ajaxSetup({},t),p=f.context||f,g=f.context&&(p.nodeType||p.jquery)?k(p):k.event,m=k.Deferred(),v=k.Callbacks("once memory"),y=f.statusCode||{},x={},w={},C="canceled",E={readyState:0,getResponseHeader:function(e){var t;if(u){if(!a)for(a={};t=Bt.exec(o);)a[t[1].toLowerCase()+" "]=(a[t[1].toLowerCase()+" "]||[]).concat(t[2]);t=a[e.toLowerCase()+" "]}return null==t?null:t.join(", ")},getAllResponseHeaders:function(){return u?o:null},setRequestHeader:function(e,t){return null==u&&(e=w[e.toLowerCase()]=w[e.toLowerCase()]||e,x[e]=t),this},overrideMimeType:function(e){return null==u&&(f.mimeType=e),this},statusCode:function(e){var t;if(e)if(u)E.always(e[E.status]);else for(t in e)y[t]=[y[t],e[t]];return this},abort:function(e){var t=e||C;return r&&r.abort(t),A(0,t),this}};if(m.promise(E),f.url=((e||f.url||Ct.href)+"").replace(jt,Ct.protocol+"//"),f.type=t.method||t.type||f.method||f.type,f.dataTypes=(f.dataType||"*").toLowerCase().match(P)||[""],null==f.crossDomain){l=b.createElement("a");try{l.href=f.url,l.href=l.href,f.crossDomain=Nt.protocol+"//"+Nt.host!=l.protocol+"//"+l.host}catch(e){f.crossDomain=!0}}if(f.data&&f.processData&&"string"!=typeof f.data&&(f.data=k.param(f.data,f.traditional)),Wt(Rt,f,t,E),u)return E;for(h in(c=k.event&&f.global)&&0==k.active++&&k.event.trigger("ajaxStart"),f.type=f.type.toUpperCase(),f.hasContent=!Mt.test(f.type),i=f.url.replace(Ot,""),f.hasContent?f.data&&f.processData&&0===(f.contentType||"").indexOf("application/x-www-form-urlencoded")&&(f.data=f.data.replace(Tt,"+")):(d=f.url.slice(i.length),f.data&&(f.processData||"string"==typeof f.data)&&(i+=(Et.test(i)?"&":"?")+f.data,delete f.data),!1===f.cache&&(i=i.replace(Lt,"$1"),d=(Et.test(i)?"&":"?")+"_="+kt.guid+++d),f.url=i+d),f.ifModified&&(k.lastModified[i]&&E.setRequestHeader("If-Modified-Since",k.lastModified[i]),k.etag[i]&&E.setRequestHeader("If-None-Match",k.etag[i])),(f.data&&f.hasContent&&!1!==f.contentType||t.contentType)&&E.setRequestHeader("Content-Type",f.contentType),E.setRequestHeader("Accept",f.dataTypes[0]&&f.accepts[f.dataTypes[0]]?f.accepts[f.dataTypes[0]]+("*"!==f.dataTypes[0]?", "+It+"; q=0.01":""):f.accepts["*"]),f.headers)E.setRequestHeader(h,f.headers[h]);if(f.beforeSend&&(!1===f.beforeSend.call(p,E,f)||u))return E.abort();if(C="abort",v.add(f.complete),E.done(f.success),E.fail(f.error),r=Wt(Pt,f,t,E)){if(E.readyState=1,c&&g.trigger("ajaxSend",[E,f]),u)return E;f.async&&f.timeout>0&&(s=n.setTimeout(function(){E.abort("timeout")},f.timeout));try{u=!1,r.send(x,A)}catch(e){if(u)throw e;A(-1,e)}}else A(-1,"No Transport");function A(e,t,a,l){var h,d,b,x,w,C=t;u||(u=!0,s&&n.clearTimeout(s),r=void 0,o=l||"",E.readyState=e>0?4:0,h=e>=200&&e<300||304===e,a&&(x=function(e,t,n){for(var r,i,o,a,s=e.contents,l=e.dataTypes;"*"===l[0];)l.shift(),void 0===r&&(r=e.mimeType||t.getResponseHeader("Content-Type"));if(r)for(i in s)if(s[i]&&s[i].test(r)){l.unshift(i);break}if(l[0]in n)o=l[0];else{for(i in n){if(!l[0]||e.converters[i+" "+l[0]]){o=i;break}a||(a=i)}o=o||a}if(o)return o!==l[0]&&l.unshift(o),n[o]}(f,E,a)),!h&&k.inArray("script",f.dataTypes)>-1&&(f.converters["text script"]=function(){}),x=function(e,t,n,r){var i,o,a,s,l,u={},c=e.dataTypes.slice();if(c[1])for(a in e.converters)u[a.toLowerCase()]=e.converters[a];for(o=c.shift();o;)if(e.responseFields[o]&&(n[e.responseFields[o]]=t),!l&&r&&e.dataFilter&&(t=e.dataFilter(t,e.dataType)),l=o,o=c.shift())if("*"===o)o=l;else if("*"!==l&&l!==o){if(!(a=u[l+" "+o]||u["* "+o]))for(i in u)if((s=i.split(" "))[1]===o&&(a=u[l+" "+s[0]]||u["* "+s[0]])){!0===a?a=u[i]:!0!==u[i]&&(o=s[0],c.unshift(s[1]));break}if(!0!==a)if(a&&e.throws)t=a(t);else try{t=a(t)}catch(e){return{state:"parsererror",error:a?e:"No conversion from "+l+" to "+o}}}return{state:"success",data:t}}(f,x,E,h),h?(f.ifModified&&((w=E.getResponseHeader("Last-Modified"))&&(k.lastModified[i]=w),(w=E.getResponseHeader("etag"))&&(k.etag[i]=w)),204===e||"HEAD"===f.type?C="nocontent":304===e?C="notmodified":(C=x.state,d=x.data,h=!(b=x.error))):(b=C,!e&&C||(C="error",e<0&&(e=0))),E.status=e,E.statusText=(t||C)+"",h?m.resolveWith(p,[d,C,E]):m.rejectWith(p,[E,C,b]),E.statusCode(y),y=void 0,c&&g.trigger(h?"ajaxSuccess":"ajaxError",[E,f,h?d:b]),v.fireWith(p,[E,C]),c&&(g.trigger("ajaxComplete",[E,f]),--k.active||k.event.trigger("ajaxStop")))}return E},getJSON:function(e,t,n){return k.get(e,t,n,"json")},getScript:function(e,t){return k.get(e,void 0,t,"script")}}),k.each(["get","post"],function(e,t){k[t]=function(e,n,r,i){return v(n)&&(i=i||r,r=n,n=void 0),k.ajax(k.extend({url:e,type:t,dataType:i,data:n,success:r},k.isPlainObject(e)&&e))}}),k.ajaxPrefilter(function(e){var t;for(t in e.headers)"content-type"===t.toLowerCase()&&(e.contentType=e.headers[t]||"")}),k._evalUrl=function(e,t,n){return k.ajax({url:e,type:"GET",dataType:"script",cache:!0,async:!1,global:!1,converters:{"text script":function(){}},dataFilter:function(e){k.globalEval(e,t,n)}})},k.fn.extend({wrapAll:function(e){var t;return this[0]&&(v(e)&&(e=e.call(this[0])),t=k(e,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){for(var e=this;e.firstElementChild;)e=e.firstElementChild;return e}).append(this)),this},wrapInner:function(e){return v(e)?this.each(function(t){k(this).wrapInner(e.call(this,t))}):this.each(function(){var t=k(this),n=t.contents();n.length?n.wrapAll(e):t.append(e)})},wrap:function(e){var t=v(e);return this.each(function(n){k(this).wrapAll(t?e.call(this,n):e)})},unwrap:function(e){return this.parent(e).not("body").each(function(){k(this).replaceWith(this.childNodes)}),this}}),k.expr.pseudos.hidden=function(e){return!k.expr.pseudos.visible(e)},k.expr.pseudos.visible=function(e){return!!(e.offsetWidth||e.offsetHeight||e.getClientRects().length)},k.ajaxSettings.xhr=function(){try{return new n.XMLHttpRequest}catch(e){}};var Ht={0:200,1223:204},Vt=k.ajaxSettings.xhr();m.cors=!!Vt&&"withCredentials"in Vt,m.ajax=Vt=!!Vt,k.ajaxTransport(function(e){var t,r;if(m.cors||Vt&&!e.crossDomain)return{send:function(i,o){var a,s=e.xhr();if(s.open(e.type,e.url,e.async,e.username,e.password),e.xhrFields)for(a in e.xhrFields)s[a]=e.xhrFields[a];for(a in e.mimeType&&s.overrideMimeType&&s.overrideMimeType(e.mimeType),e.crossDomain||i["X-Requested-With"]||(i["X-Requested-With"]="XMLHttpRequest"),i)s.setRequestHeader(a,i[a]);t=function(e){return function(){t&&(t=r=s.onload=s.onerror=s.onabort=s.ontimeout=s.onreadystatechange=null,"abort"===e?s.abort():"error"===e?"number"!=typeof s.status?o(0,"error"):o(s.status,s.statusText):o(Ht[s.status]||s.status,s.statusText,"text"!==(s.responseType||"text")||"string"!=typeof s.responseText?{binary:s.response}:{text:s.responseText},s.getAllResponseHeaders()))}},s.onload=t(),r=s.onerror=s.ontimeout=t("error"),void 0!==s.onabort?s.onabort=r:s.onreadystatechange=function(){4===s.readyState&&n.setTimeout(function(){t&&r()})},t=t("abort");try{s.send(e.hasContent&&e.data||null)}catch(e){if(t)throw e}},abort:function(){t&&t()}}}),k.ajaxPrefilter(function(e){e.crossDomain&&(e.contents.script=!1)}),k.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/\b(?:java|ecma)script\b/},converters:{"text script":function(e){return k.globalEval(e),e}}}),k.ajaxPrefilter("script",function(e){void 0===e.cache&&(e.cache=!1),e.crossDomain&&(e.type="GET")}),k.ajaxTransport("script",function(e){var t,n;if(e.crossDomain||e.scriptAttrs)return{send:function(r,i){t=k("