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