From cc8de58cff0947edb5832017627af23e50214612 Mon Sep 17 00:00:00 2001 From: Olivier Gagnon Date: Mon, 16 Aug 2021 01:35:05 -0400 Subject: [PATCH] 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