From d6016f490cbb57c2d167e0198b40997d7afd7069 Mon Sep 17 00:00:00 2001 From: Olivier Gagnon Date: Sat, 13 Mar 2021 15:10:55 -0500 Subject: [PATCH] bladeburner slowly being converted to typescript, added ScriptHackMoneyGain multiplier which is the money you actually gain from script hacks, not money drained, important for BN8 --- src/BitNode/BitNode.ts | 3 +- src/BitNode/BitNodeMultipliers.ts | 8 + src/Bladeburner.js | 1207 ++------------------ src/Bladeburner/Action.ts | 269 +++++ src/Bladeburner/BlackOperation.ts | 33 + src/Bladeburner/BlackOperations.ts | 345 ++++++ src/Bladeburner/City.ts | 172 +++ src/Bladeburner/Contract.ts | 24 + src/Bladeburner/GeneralActions.ts | 49 + src/Bladeburner/Operation.ts | 55 + src/Bladeburner/Skill.ts | 107 ++ src/Bladeburner/Skills.ts | 90 ++ src/Bladeburner/data/ActionTypes.ts | 14 + src/Bladeburner/data/Constants.ts | 79 ++ src/Bladeburner/data/Help.ts | 116 ++ src/Bladeburner/data/SkillNames.ts | 31 + src/Bladeburner/ui/BlackOperationsPage.tsx | 63 + src/Constants.ts | 6 + src/NetscriptFunctions.js | 39 +- src/PersonObjects/IPlayer.ts | 1 + 20 files changed, 1565 insertions(+), 1146 deletions(-) create mode 100644 src/Bladeburner/Action.ts create mode 100644 src/Bladeburner/BlackOperation.ts create mode 100644 src/Bladeburner/BlackOperations.ts create mode 100644 src/Bladeburner/City.ts create mode 100644 src/Bladeburner/Contract.ts create mode 100644 src/Bladeburner/GeneralActions.ts create mode 100644 src/Bladeburner/Operation.ts create mode 100644 src/Bladeburner/Skill.ts create mode 100644 src/Bladeburner/Skills.ts create mode 100644 src/Bladeburner/data/ActionTypes.ts create mode 100644 src/Bladeburner/data/Constants.ts create mode 100644 src/Bladeburner/data/Help.ts create mode 100644 src/Bladeburner/data/SkillNames.ts create mode 100644 src/Bladeburner/ui/BlackOperationsPage.tsx diff --git a/src/BitNode/BitNode.ts b/src/BitNode/BitNode.ts index b46e5b2fe..28d0e3359 100644 --- a/src/BitNode/BitNode.ts +++ b/src/BitNode/BitNode.ts @@ -353,7 +353,8 @@ export function initBitNodeMultipliers(p: IPlayer) { BitNodeMultipliers.DaedalusAugsRequirement = 1.166; // Results in 35 Augs needed break; case 8: // Ghost of Wall Street - BitNodeMultipliers.ScriptHackMoney = 0; + BitNodeMultipliers.ScriptHackMoney = 0.3; + BitNodeMultipliers.ScriptHackMoneyGain = 0; BitNodeMultipliers.ManualHackMoney = 0; BitNodeMultipliers.CompanyWorkMoney = 0; BitNodeMultipliers.CrimeMoney = 0; diff --git a/src/BitNode/BitNodeMultipliers.ts b/src/BitNode/BitNodeMultipliers.ts index 0a9334d59..52d7badf1 100644 --- a/src/BitNode/BitNodeMultipliers.ts +++ b/src/BitNode/BitNodeMultipliers.ts @@ -170,6 +170,13 @@ interface IBitNodeMultipliers { */ ScriptHackMoney: number; + /** + * The amount of money actually gained when script hack a server. This is + * different than the above because you can reduce the amount of money but + * not gain that same amount. + */ + ScriptHackMoneyGain: number; + /** * Influences the growth percentage per cycle against a server. */ @@ -233,6 +240,7 @@ export const BitNodeMultipliers: IBitNodeMultipliers = { HacknetNodeMoney: 1, ManualHackMoney: 1, ScriptHackMoney: 1, + ScriptHackMoneyGain: 1, CodingContractMoney: 1, ClassGymExpGain: 1, diff --git a/src/Bladeburner.js b/src/Bladeburner.js index 10b6dae41..2ff0b5850 100644 --- a/src/Bladeburner.js +++ b/src/Bladeburner.js @@ -21,6 +21,19 @@ import { import { setTimeoutRef } from "./utils/SetTimeoutRef"; import { formatNumber } from "../utils/StringHelperFunctions"; +import { ConsoleHelpText } from "./Bladeburner/data/Help"; +import { City } from "./Bladeburner/City"; +import { BladeburnerConstants } from "./Bladeburner/data/Constants"; +import { Skill } from "./Bladeburner/Skill"; +import { Skills } from "./Bladeburner/Skills"; +import { SkillNames } from "./Bladeburner/data/SkillNames"; +import { Operation } from "./Bladeburner/Operation"; +import { BlackOperation } from "./Bladeburner/BlackOperation"; +import { BlackOperations } from "./Bladeburner/BlackOperations"; +import { Contract } from "./Bladeburner/Contract"; +import { GeneralActions } from "./Bladeburner/GeneralActions"; +import { ActionTypes } from "./Bladeburner/data/ActionTypes"; + import { addOffset } from "../utils/helpers/addOffset"; import { clearObject } from "../utils/helpers/clearObject"; import { createProgressBarText } from "../utils/helpers/createProgressBarText"; @@ -40,131 +53,11 @@ import { removeElementById } from "../utils/uiHelpers/removeElementById"; const stealthIcon = ` ` const killIcon = `` -const CityNames = ["Aevum", "Chongqing", "Sector-12", "New Tokyo", "Ishima", "Volhaven"]; - -const CyclesPerSecond = 5; // Game cycle is 200 ms - -const StaminaGainPerSecond = 0.0085; -const BaseStaminaLoss = 0.285; // Base stamina loss per action. Increased based on difficulty -const MaxStaminaToGainFactor = 70000; // Max Stamina is divided by this to get bonus stamina gain - -const DifficultyToTimeFactor = 10; // Action Difficulty divided by this to get base action time - -/** - * The difficulty multiplier affects stamina loss and hp loss of an action. Also affects - * experience gain. Its formula is: - * difficulty ^ exponentialFactor + difficulty / linearFactor - */ -const DiffMultExponentialFactor = 0.28; -const DiffMultLinearFactor = 650; - -/** - * These factors are used to calculate action time. - * They affect how much action time is reduced based on your agility and dexterity - */ -const EffAgiLinearFactor = 10e3; -const EffDexLinearFactor = 10e3; -const EffAgiExponentialFactor = 0.04; -const EffDexExponentialFactor = 0.035; - -const BaseRecruitmentTimeNeeded = 300; // Base time needed (s) to complete a Recruitment action - -const PopulationThreshold = 1e9; // Population which determines baseline success rate -const PopulationExponent = 0.7; // Exponent that influences how different populations affect success rate -const ChaosThreshold = 50; // City chaos level after which it starts making tasks harder - -const BaseStatGain = 1; // Base stat gain per second -const BaseIntGain = 0.001; // Base intelligence stat gain - -const ActionCountGrowthPeriod = 480; // Time (s) it takes for action count to grow by its specified value - -const RankToFactionRepFactor = 2; // Delta Faction Rep = this * Delta Rank -const RankNeededForFaction = 25; - -const ContractSuccessesPerLevel = 3; // How many successes you need to level up a contract -const OperationSuccessesPerLevel = 2.5; // How many successes you need to level up an op - -const RanksPerSkillPoint = 3; // How many ranks needed to get 1 Skill Point - -const ContractBaseMoneyGain = 250e3; // Base Money Gained per contract - -const HrcHpGain = 2; // HP Gained from Hyperbolic Regeneration chamber -const HrcStaminaGain = 1; // Percentage Stamina gained from Hyperbolic Regeneration Chamber - // DOM related variables const ActiveActionCssClass = "bladeburner-active-action"; - // Console related stuff let consoleHistoryIndex = 0; -const consoleHelpText = { - helpList:"Use 'help [command]' to get more information about a particular Bladeburner console command.

" + - "automate [var] [val] [hi/low] Configure simple automation for Bladeburner tasks
" + - "clear/cls Clear the console
" + - "help [cmd] Display this help text, or help text for a specific command
" + - "log [en/dis] [type] Enable or disable logging for events and actions
" + - "skill [action] [name] Level or display info about your Bladeburner skills
" + - "start [type] [name] Start a Bladeburner action/task
" + - "stop Stops your current Bladeburner action/task
", - automate:"automate [var] [val] [hi/low]

" + - "A simple way to automate your Bladeburner actions. This console command can be used " + - "to automatically start an action when your stamina rises above a certain threshold, and " + - "automatically switch to another action when your stamina drops below another threshold.

" + - "automate status - Check the current status of your automation and get a brief description of what it'll do
" + - "automate en - Enable the automation feature
" + - "automate dis - Disable the automation feature

" + - "There are four properties that must be set for this automation to work properly. Here is how to set them:

" + - "automate stamina 100 high
" + - "automate contract Tracking high
" + - "automate stamina 50 low
" + - 'automate general "Field Analysis" low

' + - "Using the four console commands above will set the automation to perform Tracking contracts " + - "if your stamina is 100 or higher, and then switch to Field Analysis if your stamina drops below " + - "50. Note that when setting the action, the name of the action is CASE-SENSITIVE. It must " + - "exactly match whatever the name is in the UI.", - clear:"clear

Clears the console", - cls:"cls

Clears the console", - help:"help [command]

" + - "Running 'help' with no arguments displays the general help text, which lists all console commands " + - "and a brief description of what they do. A command can be specified to get more specific help text " + - "about that particular command. For example:

" + - "help automate

" + - "will display specific information about using the automate console command", - log:"log [en/dis] [type]

" + - "Enable or disable logging. By default, the results of completing actions such as contracts/operations are logged " + - "in the console. There are also random events that are logged in the console as well. The five categories of " + - "things that get logged are:

" + - "[general, contracts, ops, blackops, events]

" + - "The logging for these categories can be enabled or disabled like so:

" + - "log dis contracts - Disables logging that occurs when contracts are completed
" + - "log en contracts - Enables logging that occurs when contracts are completed
" + - "log dis events - Disables logging for Bladeburner random events

" + - "Logging can be universally enabled/disabled using the 'all' keyword:

" + - "log dis all
" + - "log en all", - skill:"skill [action] [name]

" + - "Level or display information about your skills.

" + - "To display information about all of your skills and your multipliers, use:

" + - "skill list

" + - "To display information about a specific skill, specify the name of the skill afterwards. " + - "Note that the name of the skill is case-sensitive. Enter it exactly as seen in the UI. If " + - "the name of the skill has whitespace, enclose the name of the skill in double quotation marks:

" + - "skill list Reaper
" + - 'skill list "Digital Observer"

' + - "This console command can also be used to level up skills:

" + - "skill level [skill name]", - start:"start [type] [name]

" + - "Start an action. An action is specified by its type and its name. The " + - "name is case-sensitive. It must appear exactly as it does in the UI. If " + - "the name of the action has whitespace, enclose it in double quotation marks. " + - "Valid action types include:

" + - "[general, contract, op, blackop]

" + - "Examples:

" + - 'start contract Tracking
' + - 'start op "Undercover Operation"
', - stop:"stop

" + - "Stop your current action and go idle", -} // Keypresses for Console $(document).keydown(function(event) { @@ -224,405 +117,6 @@ $(document).keydown(function(event) { } }); -function City(params={}) { - this.name = params.name ? params.name : CityNames[2]; // Sector-12 - - // Synthoid population and estimate - this.pop = params.pop ? params.pop : getRandomInt(PopulationThreshold, 1.5 * PopulationThreshold); - this.popEst = this.pop * (Math.random() + 0.5); - - // Number of Synthoid communities population and estimate - this.comms = params.comms ? params.comms : getRandomInt(5, 150); - this.commsEst = this.comms + getRandomInt(-5, 5); - if (this.commsEst < 0) {this.commsEst = 0;} - this.chaos = 0; -} - -City.prototype.improvePopulationEstimateByCount = function(n) { - if (isNaN(n)) {throw new Error("NaN passeed into City.improvePopulationEstimateByCount()");} - if (this.popEst < this.pop) { - this.popEst += n; - if (this.popEst > this.pop) {this.popEst = this.pop;} - } else if (this.popEst > this.pop) { - this.popEst -= n; - if (this.popEst < this.pop) {this.popEst = this.pop;} - } -} - -// @p is the percentage, not the multiplier. e.g. pass in p = 5 for 5% -City.prototype.improvePopulationEstimateByPercentage = function(p, skillMult=1) { - p = p*skillMult; - if (isNaN(p)) {throw new Error("NaN passed into City.improvePopulationEstimateByPercentage()");} - if (this.popEst < this.pop) { - ++this.popEst; // In case estimate is 0 - this.popEst *= (1 + (p/100)); - if (this.popEst > this.pop) {this.popEst = this.pop;} - } else if (this.popEst > this.pop) { - this.popEst *= (1 - (p/100)); - if (this.popEst < this.pop) {this.popEst = this.pop;} - } -} - -City.prototype.improveCommunityEstimate = function(n=1) { - if (isNaN(n)) {throw new Error("NaN passed into City.improveCommunityEstimate()");} - if (this.commsEst < this.comms) { - this.commsEst += n; - if (this.commsEst > this.comms) {this.commsEst = this.comms;} - } else if (this.commsEst > this.comms) { - this.commsEst -= n; - if (this.commsEst < this.comms) {this.commsEst = this.comms;} - } -} - -/** - * @params options: - * estChange(int): How much the estimate should change by - * estOffset(int): Add offset to estimate (offset by percentage) - */ -City.prototype.changePopulationByCount = function(n, params={}) { - if (isNaN(n)) {throw new Error("NaN passed into City.changePopulationByCount()");} - this.pop += n; - if (params.estChange && !isNaN(params.estChange)) {this.popEst += params.estChange;} - if (params.estOffset) { - this.popEst = addOffset(this.popEst, params.estOffset); - } - this.popEst = Math.max(this.popEst, 0); -} - -/** - * @p is the percentage, not the multiplier. e.g. pass in p = 5 for 5% - * @params options: - * changeEstEqually(bool) - Change the population estimate by an equal amount - * nonZero (bool) - Set to true to ensure that population always changes by at least 1 - */ -City.prototype.changePopulationByPercentage = function(p, params={}) { - if (isNaN(p)) {throw new Error("NaN passed into City.changePopulationByPercentage()");} - if (p === 0) {return;} - var change = Math.round(this.pop * (p/100)); - - // Population always changes by at least 1 - if (params.nonZero && change === 0) { - p > 0 ? change = 1 : change = -1; - } - - this.pop += change; - if (params.changeEstEqually) { - this.popEst += change; - if (this.popEst < 0) {this.popEst = 0;} - } - return change; -} - -City.prototype.changeChaosByCount = function(n) { - if (isNaN(n)) {throw new Error("NaN passed into City.changeChaosByCount()");} - if (n === 0) {return;} - this.chaos += n; - if (this.chaos < 0) {this.chaos = 0;} -} - -// @p is the percentage, not the multiplier (e.g. pass in p = 5 for 5%) -City.prototype.changeChaosByPercentage = function(p) { - if (isNaN(p)) {throw new Error("NaN passed into City.chaosChaosByPercentage()");} - if (p === 0) {return;} - var change = this.chaos * (p/100); - this.chaos += change; - if (this.chaos < 0) {this.chaos = 0;} -} - -City.prototype.toJSON = function() { - return Generic_toJSON("City", this); -} -City.fromJSON = function(value) { - return Generic_fromJSON(City, value.data); -} -Reviver.constructors.City = City; - -function Skill(params={name:"foo", desc:"foo"}) { - if (params.name) { - this.name = params.name; - } else { - throw new Error("Failed to initialize Bladeburner Skill. No name was specified in ctor"); - } - if (params.desc) { - this.desc = params.desc; - } else { - throw new Error("Failed to initialize Bladeburner Skills. No desc was specified in ctor"); - } - this.baseCost = params.baseCost ? params.baseCost : 1; // Cost is in Skill Points - this.costInc = params.costInc ? params.costInc : 1; // Additive cost increase per level - - if (params.maxLvl) {this.maxLvl = params.maxLvl;} - - /** - * These benefits are additive. So total multiplier will be level (handled externally) times the - * effects below - */ - if (params.successChanceAll) {this.successChanceAll = params.successChanceAll;} - if (params.successChanceStealth) {this.successChanceStealth = params.successChanceStealth;} - if (params.successChanceKill) {this.successChanceKill = params.successChanceKill;} - if (params.successChanceContract) {this.successChanceContract = params.successChanceContract;} - if (params.successChanceOperation) {this.successChanceOperation = params.successChanceOperation;} - - /** - * This multiplier affects everything that increases synthoid population/community estimate - * e.g. Field analysis, Investigation Op, Undercover Op - */ - if (params.successChanceEstimate) {this.successChanceEstimate = params.successChanceEstimate;} - - if (params.actionTime) {this.actionTime = params.actionTime;} - if (params.effHack) {this.effHack = params.effHack;} - if (params.effStr) {this.effStr = params.effStr;} - if (params.effDef) {this.effDef = params.effDef;} - if (params.effDex) {this.effDex = params.effDex;} - if (params.effAgi) {this.effAgi = params.effAgi;} - if (params.effCha) {this.effCha = params.effCha;} - - if (params.stamina) {this.stamina = params.stamina;} - if (params.money) {this.money = params.money;} - if (params.expGain) {this.expGain = params.expGain;} - - //Equipment - if (params.weaponAbility) {this.weaponAbility = params.weaponAbility;} - if (params.gunAbility) {this.gunAbility = params.gunAbility;} -} - -Skill.prototype.calculateCost = function(currentLevel) { - return Math.floor((this.baseCost + (currentLevel * this.costInc)) * BitNodeMultipliers.BladeburnerSkillCost); -} -const Skills = {}; -const SkillNames = { - BladesIntuition: "Blade's Intuition", - Cloak: "Cloak", - Marksman: "Marksman", - WeaponProficiency: "Weapon Proficiency", - ShortCircuit: "Short-Circuit", - DigitalObserver: "Digital Observer", - Tracer: "Tracer", - Overclock: "Overclock", - Reaper: "Reaper", - EvasiveSystem: "Evasive System", - Datamancer: "Datamancer", - CybersEdge: "Cyber's Edge", - HandsOfMidas: "Hands of Midas", - Hyperdrive: "Hyperdrive", -} - -// Base Class for Contracts, Operations, and BlackOps -function Action(params={}) { - this.name = params.name ? params.name : ""; - this.desc = params.desc ? params.desc : ""; - - // Difficulty scales with level. See getDifficulty() method - this.level = 1; - this.maxLevel = 1; - this.autoLevel = true; - this.baseDifficulty = params.baseDifficulty ? addOffset(params.baseDifficulty, 10) : 100; - this.difficultyFac = params.difficultyFac ? params.difficultyFac : 1.01; - - // Rank increase/decrease is affected by this exponent - this.rewardFac = params.rewardFac ? params.rewardFac : 1.02; - - this.successes = 0; - this.failures = 0; - - // All of these scale with level/difficulty - this.rankGain = params.rankGain ? params.rankGain : 0; - if (params.rankLoss) {this.rankLoss = params.rankLoss;} - if (params.hpLoss) { - this.hpLoss = params.hpLoss; - this.hpLost = 0; - } - - // Action Category. Current categories are stealth and kill - this.isStealth = params.isStealth ? true : false; - this.isKill = params.isKill ? true : false; - - /** - * Number of this contract remaining, and its growth rate - * Growth rate is an integer and the count will increase by that integer every "cycle" - */ - this.count = params.count ? params.count : getRandomInt(1e3, 25e3); - this.countGrowth = params.countGrowth ? params.countGrowth : getRandomInt(1, 5); - - // Weighting of each stat in determining action success rate - var defaultWeights = {hack:1/7,str:1/7,def:1/7,dex:1/7,agi:1/7,cha:1/7,int:1/7}; - this.weights = params.weights ? params.weights : defaultWeights; - - // Check to make sure weights are summed properly - let sum = 0; - for (const weight in this.weights) { - if (this.weights.hasOwnProperty(weight)) { - sum += this.weights[weight]; - } - } - if (sum - 1 >= 10 * Number.EPSILON) { - throw new Error("Invalid weights when constructing Action " + this.name + - ". The weights should sum up to 1. They sum up to :" + 1); - } - - // Diminishing returns of stats (stat ^ decay where 0 <= decay <= 1) - const defaultDecays = { hack: 0.9, str: 0.9, def: 0.9, dex: 0.9, agi: 0.9, cha: 0.9, int: 0.9 }; - this.decays = params.decays ? params.decays : defaultDecays; - for (const decay in this.decays) { - if (this.decays.hasOwnProperty(decay)) { - if (this.decays[decay] > 1) { - throw new Error("Invalid decays when constructing " + - "Action " + this.name + ". " + - "Decay value cannot be greater than 1"); - } - } - } -} - -Action.prototype.getDifficulty = function() { - var difficulty = this.baseDifficulty * Math.pow(this.difficultyFac, this.level-1); - if (isNaN(difficulty)) {throw new Error("Calculated NaN in Action.getDifficulty()");} - return difficulty; -} - -/** - * @inst - Bladeburner Object - * @params - options: - * est (bool): Get success chance estimate instead of real success chance - */ -Action.prototype.getSuccessChance = function(inst, params={}) { - if (inst == null) {throw new Error("Invalid Bladeburner instance passed into Action.getSuccessChance");} - var difficulty = this.getDifficulty(); - var competence = 0; - for (var stat in this.weights) { - if (this.weights.hasOwnProperty(stat)) { - var playerStatLvl = Player.queryStatFromString(stat); - var key = "eff" + stat.charAt(0).toUpperCase() + stat.slice(1); - var effMultiplier = inst.skillMultipliers[key]; - if (effMultiplier == null) { - console.error(`Failed to find Bladeburner Skill multiplier for: ${stat}`); - effMultiplier = 1; - } - competence += (this.weights[stat] * Math.pow(effMultiplier*playerStatLvl, this.decays[stat])); - } - } - competence *= inst.calculateStaminaPenalty(); - - // For Operations, factor in team members - if (this instanceof Operation || this instanceof BlackOperation) { - if (this.teamCount && this.teamCount > 0) { - this.teamCount = Math.min(this.teamCount, inst.teamSize); - var teamMultiplier = Math.pow(this.teamCount, 0.05); - competence *= teamMultiplier; - } - } - - // Lower city population results in lower chances - if (!(this instanceof BlackOperation)) { - var city = inst.getCurrentCity(); - if (params.est) { - competence *= Math.pow((city.popEst / PopulationThreshold), PopulationExponent); - } else { - competence *= Math.pow((city.pop / PopulationThreshold), PopulationExponent); - } - - // Too high of a chaos results in lower chances - if (city.chaos > ChaosThreshold) { - var diff = 1 + (city.chaos - ChaosThreshold); - var mult = Math.pow(diff, 0.1); - difficulty *= mult; - } - - // For Raid Operations, no communities = fail - if (this instanceof Operation && this.name === "Raid") { - if (city.comms <= 0) {return 0;} - } - } - - // Factor skill multipliers into success chance - competence *= inst.skillMultipliers.successChanceAll; - if (this instanceof Operation || this instanceof BlackOperation) { - competence *= inst.skillMultipliers.successChanceOperation; - } - if (this instanceof Contract) { - competence *= inst.skillMultipliers.successChanceContract; - } - if (this.isStealth) { - competence *= inst.skillMultipliers.successChanceStealth; - } - if (this.isKill) { - competence *= inst.skillMultipliers.successChanceKill; - } - - // Augmentation multiplier - competence *= Player.bladeburner_success_chance_mult; - - if (isNaN(competence)) {throw new Error("Competence calculated as NaN in Action.getSuccessChance()");} - return Math.min(1, competence / difficulty); -} - -/** - * Tests for success. Should be called when an action has completed - * @param inst {Bladeburner} - Bladeburner instance - */ -Action.prototype.attempt = function(inst) { - return (Math.random() < this.getSuccessChance(inst)); -} - -Action.prototype.getActionTime = function(inst) { - var difficulty = this.getDifficulty(); - var baseTime = difficulty / DifficultyToTimeFactor; - var skillFac = inst.skillMultipliers.actionTime; // Always < 1 - - var effAgility = Player.agility * inst.skillMultipliers.effAgi; - var effDexterity = Player.dexterity * inst.skillMultipliers.effDex; - var statFac = 0.5 * (Math.pow(effAgility, EffAgiExponentialFactor) + - Math.pow(effDexterity, EffDexExponentialFactor) + - (effAgility / EffAgiLinearFactor) + - (effDexterity / EffDexLinearFactor)); // Always > 1 - - baseTime = Math.max(1, baseTime * skillFac / statFac); - - if (this instanceof Contract) { - return Math.ceil(baseTime); - } else if (this instanceof Operation) { - return Math.ceil(baseTime); - } else if (this instanceof BlackOperation) { - return Math.ceil(baseTime * 1.5); - } else { - throw new Error("Unrecognized Action Type in Action.getActionTime(this). Must be either Contract, Operation, or BlackOperation"); - } -} - -Action.prototype.getSuccessesNeededForNextLevel = function(baseSuccessesPerLevel) { - return Math.ceil((0.5) * (this.maxLevel) * (2 * baseSuccessesPerLevel + (this.maxLevel-1))); -} - -Action.prototype.setMaxLevel = function(baseSuccessesPerLevel) { - if (this.successes >= this.getSuccessesNeededForNextLevel(baseSuccessesPerLevel)) { - ++this.maxLevel; - } -} - -Action.prototype.toJSON = function() { - return Generic_toJSON("Action", this); -} -Action.fromJSON = function(value) { - return Generic_fromJSON(Action, value.data); -} -Reviver.constructors.Action = Action; -const GeneralActions = {}; // Training, Field Analysis, Recruitment, etc. - -// Action Identifier enum -const ActionTypes = Object.freeze({ - "Idle": 1, - "Contract": 2, - "Operation": 3, - "BlackOp": 4, - "BlackOperation": 4, - "Training": 5, - "Recruitment": 6, - "FieldAnalysis": 7, - "Field Analysis": 7, - "Diplomacy": 8, - "Hyperbolic Regeneration Chamber": 9, -}); - function ActionIdentifier(params={}) { if (params.name) {this.name = params.name;} if (params.type) {this.type = params.type;} @@ -638,65 +132,6 @@ ActionIdentifier.fromJSON = function(value) { Reviver.constructors.ActionIdentifier = ActionIdentifier; -// Contracts -function Contract(params={}) { - Action.call(this, params); -} - -Contract.prototype = Object.create(Action.prototype); - -Contract.prototype.toJSON = function() { - return Generic_toJSON("Contract", this); -} - -Contract.fromJSON = function(value) { - return Generic_fromJSON(Contract, value.data); -} - -Reviver.constructors.Contract = Contract; - -// Operations -function Operation(params={}) { - Action.call(this, params); - this.reqdRank = params.reqdRank ? params.reqdRank : 100; - this.teamCount = params.teamCount ? params.teamCount : 0; //# of team members to use -} - -Operation.prototype = Object.create(Action.prototype); - -Operation.prototype.toJSON = function() { - return Generic_toJSON("Operation", this); -} - -Operation.fromJSON = function(value) { - return Generic_fromJSON(Operation, value.data); -} - -Reviver.constructors.Operation = Operation; - -// Black Operations -function BlackOperation(params={}) { - Operation.call(this, params); - - // Black ops are one time missions - this.count = 1; - this.countGrowth = 0; -} - -BlackOperation.prototype = Object.create(Action.prototype); - -BlackOperation.prototype.toJSON = function() { - return Generic_toJSON("BlackOperation", this); -} - -BlackOperation.fromJSON = function(value) { - return Generic_fromJSON(BlackOperation, value.data); -} - -Reviver.constructors.BlackOperation = BlackOperation; - -const BlackOperations = {}; - function Bladeburner(params={}) { this.numHosp = 0; // Number of hospitalizations this.moneyLost = 0; // Money lost due to hospitalizations @@ -722,10 +157,10 @@ function Bladeburner(params={}) { this.action = new ActionIdentifier({type:idleActionType}); this.cities = {}; - for (var i = 0; i < CityNames.length; ++i) { - this.cities[CityNames[i]] = new City({name:CityNames[i]}); + for (var i = 0; i < BladeburnerConstants.CityNames.length; ++i) { + this.cities[BladeburnerConstants.CityNames[i]] = new City({name: BladeburnerConstants.CityNames[i]}); } - this.city = CityNames[2]; // Sector-12 + this.city = BladeburnerConstants.CityNames[2]; // Sector-12 // Map of SkillNames -> level this.skills = {}; @@ -770,7 +205,6 @@ function Bladeburner(params={}) { this.consoleLogs = []; // Initialization - initBladeburner(); this.initializeDomElementRefs(); if (params.new) {this.create();} } @@ -778,7 +212,7 @@ function Bladeburner(params={}) { Bladeburner.prototype.prestige = function() { this.resetAction(); var bladeburnerFac = Factions["Bladeburners"]; - if (this.rank >= RankNeededForFaction) { + if (this.rank >= BladeburnerConstants.RankNeededForFaction) { joinFaction(bladeburnerFac); } } @@ -902,6 +336,7 @@ Bladeburner.prototype.storeCycles = function(numCycles=1) { this.storedCycles += numCycles; } + Bladeburner.prototype.process = function() { // Edge case condition...if Operation Daedalus is complete trigger the BitNode if (redPillFlag === false && this.blackops.hasOwnProperty("Operation Daedalus")) { @@ -928,10 +363,10 @@ Bladeburner.prototype.process = function() { } // A 'tick' for this mechanic is one second (= 5 game cycles) - if (this.storedCycles >= CyclesPerSecond) { - var seconds = Math.floor(this.storedCycles / CyclesPerSecond); + if (this.storedCycles >= BladeburnerConstants.CyclesPerSecond) { + var seconds = Math.floor(this.storedCycles / BladeburnerConstants.CyclesPerSecond); seconds = Math.min(seconds, 5); // Max of 5 'ticks' - this.storedCycles -= seconds * CyclesPerSecond; + this.storedCycles -= seconds * BladeburnerConstants.CyclesPerSecond; // Stamina this.calculateMaxStamina(); @@ -942,19 +377,19 @@ Bladeburner.prototype.process = function() { for (var contractName in this.contracts) { if (this.contracts.hasOwnProperty(contractName)) { var contract = this.contracts[contractName]; - contract.count += (seconds * contract.countGrowth/ActionCountGrowthPeriod); + contract.count += (seconds * contract.countGrowth/BladeburnerConstants.ActionCountGrowthPeriod); } } for (var operationName in this.operations) { if (this.operations.hasOwnProperty(operationName)) { var op = this.operations[operationName]; - op.count += (seconds * op.countGrowth/ActionCountGrowthPeriod); + op.count += (seconds * op.countGrowth/BladeburnerConstants.ActionCountGrowthPeriod); } } // Chaos goes down very slowly - for (var i = 0; i < CityNames.length; ++i) { - var city = this.cities[CityNames[i]]; + for (var i = 0; i < BladeburnerConstants.CityNames.length; ++i) { + var city = this.cities[BladeburnerConstants.CityNames[i]]; 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); @@ -1006,8 +441,8 @@ Bladeburner.prototype.calculateMaxStamina = function() { Bladeburner.prototype.calculateStaminaGainPerSecond = function() { var effAgility = Player.agility * this.skillMultipliers.effAgi; - var maxStaminaBonus = this.maxStamina / MaxStaminaToGainFactor; - var gain = (StaminaGainPerSecond + maxStaminaBonus) * Math.pow(effAgility, 0.17); + var maxStaminaBonus = this.maxStamina / BladeburnerConstants.MaxStaminaToGainFactor; + var gain = (BladeburnerConstants.StaminaGainPerSecond + maxStaminaBonus) * Math.pow(effAgility, 0.17); return gain * (this.skillMultipliers.stamina * Player.bladeburner_stamina_gain_mult); } @@ -1029,15 +464,15 @@ Bladeburner.prototype.changeRank = function(change) { } if (bladeburnerFac.isMember) { var favorBonus = 1 + (bladeburnerFac.favor / 100); - bladeburnerFac.playerReputation += (RankToFactionRepFactor * change * Player.faction_rep_mult * favorBonus); + bladeburnerFac.playerReputation += (BladeburnerConstants.RankToFactionRepFactor * change * Player.faction_rep_mult * favorBonus); } } // Gain skill points - var rankNeededForSp = (this.totalSkillPoints+1) * RanksPerSkillPoint; + var rankNeededForSp = (this.totalSkillPoints+1) * BladeburnerConstants.RanksPerSkillPoint; if (this.maxRank >= rankNeededForSp) { // Calculate how many skill points to gain - var gainedSkillPoints = Math.floor((this.maxRank - rankNeededForSp) / RanksPerSkillPoint + 1); + var gainedSkillPoints = Math.floor((this.maxRank - rankNeededForSp) / BladeburnerConstants.RanksPerSkillPoint + 1); this.skillPoints += gainedSkillPoints; this.totalSkillPoints += gainedSkillPoints; } @@ -1070,8 +505,6 @@ Bladeburner.prototype.resetSkillMultipliers = function() { stamina: 1, money: 1, expGain: 1, - weaponAbility: 1, - gunAbility: 1, }; } @@ -1241,11 +674,11 @@ Bladeburner.prototype.completeAction = function() { throw new Error("Failed to get Contract/Operation Object for: " + this.action.name); } var difficulty = action.getDifficulty(); - var difficultyMultiplier = Math.pow(difficulty, DiffMultExponentialFactor) + difficulty / DiffMultLinearFactor; + var difficultyMultiplier = Math.pow(difficulty, BladeburnerConstants.DiffMultExponentialFactor) + difficulty / BladeburnerConstants.DiffMultLinearFactor; var rewardMultiplier = Math.pow(action.rewardFac, action.level-1); // Stamina loss is based on difficulty - this.stamina -= (BaseStaminaLoss * difficultyMultiplier); + this.stamina -= (BladeburnerConstants.BaseStaminaLoss * difficultyMultiplier); if (this.stamina < 0) {this.stamina = 0;} // Process Contract/Operation success/failure @@ -1257,15 +690,15 @@ Bladeburner.prototype.completeAction = function() { // Earn money for contracts var moneyGain = 0; if (!isOperation) { - moneyGain = ContractBaseMoneyGain * rewardMultiplier * this.skillMultipliers.money; + moneyGain = BladeburnerConstants.ContractBaseMoneyGain * rewardMultiplier * this.skillMultipliers.money; Player.gainMoney(moneyGain); Player.recordMoneySource(moneyGain, "bladeburner"); } if (isOperation) { - action.setMaxLevel(OperationSuccessesPerLevel); + action.setMaxLevel(BladeburnerConstants.OperationSuccessesPerLevel); } else { - action.setMaxLevel(ContractSuccessesPerLevel); + action.setMaxLevel(BladeburnerConstants.ContractSuccessesPerLevel); } if (action.rankGain) { var gain = addOffset(action.rankGain * rewardMultiplier * BitNodeMultipliers.BladeburnerRank, 10); @@ -1318,10 +751,10 @@ Bladeburner.prototype.completeAction = function() { throw new Error("Failed to get BlackOperation Object for: " + this.action.name); } var difficulty = action.getDifficulty(); - var difficultyMultiplier = Math.pow(difficulty, DiffMultExponentialFactor) + difficulty / DiffMultLinearFactor; + var difficultyMultiplier = Math.pow(difficulty, BladeburnerConstants.DiffMultExponentialFactor) + difficulty / BladeburnerConstants.DiffMultLinearFactor; // Stamina loss is based on difficulty - this.stamina -= (BaseStaminaLoss * difficultyMultiplier); + this.stamina -= (BladeburnerConstants.BaseStaminaLoss * difficultyMultiplier); if (this.stamina < 0) {this.stamina = 0;} // Team loss variables @@ -1389,7 +822,7 @@ Bladeburner.prototype.completeAction = function() { } break; case ActionTypes["Training"]: - this.stamina -= (0.5 * BaseStaminaLoss); + this.stamina -= (0.5 * BladeburnerConstants.BaseStaminaLoss); var strExpGain = 30 * Player.strength_exp_mult, defExpGain = 30 * Player.defense_exp_mult, dexExpGain = 30 * Player.dexterity_exp_mult, @@ -1423,7 +856,7 @@ Bladeburner.prototype.completeAction = function() { var hackingExpGain = 20 * Player.hacking_exp_mult, charismaExpGain = 20 * Player.charisma_exp_mult; Player.gainHackingExp(hackingExpGain); - Player.gainIntelligenceExp(BaseIntGain); + Player.gainIntelligenceExp(BladeburnerConstants.BaseIntGain); Player.gainCharismaExp(charismaExpGain); this.changeRank(0.1 * BitNodeMultipliers.BladeburnerRank); this.getCurrentCity().improvePopulationEstimateByPercentage(eff * this.skillMultipliers.successChanceEstimate); @@ -1435,14 +868,14 @@ Bladeburner.prototype.completeAction = function() { case ActionTypes["Recruitment"]: var successChance = this.getRecruitmentSuccessChance(); if (Math.random() < successChance) { - var expGain = 2 * BaseStatGain * this.actionTimeToComplete; + var expGain = 2 * BladeburnerConstants.BaseStatGain * this.actionTimeToComplete; Player.gainCharismaExp(expGain); ++this.teamSize; if (this.logging.general) { this.log("Successfully recruited a team member! Gained " + formatNumber(expGain, 1) + " charisma exp"); } } else { - var expGain = BaseStatGain * this.actionTimeToComplete; + var expGain = BladeburnerConstants.BaseStatGain * this.actionTimeToComplete; Player.gainCharismaExp(expGain); if (this.logging.general) { this.log("Failed to recruit a team member. Gained " + formatNumber(expGain, 1) + " charisma exp"); @@ -1460,13 +893,13 @@ Bladeburner.prototype.completeAction = function() { this.startAction(this.action); // Repeat Action break; case ActionTypes["Hyperbolic Regeneration Chamber"]: { - Player.regenerateHp(HrcHpGain); + Player.regenerateHp(BladeburnerConstants.HrcHpGain); - const staminaGain = this.maxStamina * (HrcStaminaGain / 100); + const staminaGain = this.maxStamina * (BladeburnerConstants.HrcStaminaGain / 100); this.stamina = Math.min(this.maxStamina, this.stamina + staminaGain); this.startAction(this.action); if (this.logging.general) { - this.log(`Rested in Hyperbolic Regeneration Chamber. Restored ${HrcHpGain} HP and gained ${numeralWrapper.format(staminaGain, "0.0")} stamina`); + this.log(`Rested in Hyperbolic Regeneration Chamber. Restored ${BladeburnerConstants.HrcHpGain} HP and gained ${numeralWrapper.format(staminaGain, "0.0")} stamina`); } break; } @@ -1585,7 +1018,7 @@ Bladeburner.prototype.completeOperation = function(success) { Bladeburner.prototype.getRecruitmentTime = function() { var effCharisma = Player.charisma * this.skillMultipliers.effCha; var charismaFactor = Math.pow(effCharisma, 0.81) + effCharisma / 90; - return Math.max(10, Math.round(BaseRecruitmentTimeNeeded - charismaFactor)); + return Math.max(10, Math.round(BladeburnerConstants.BaseRecruitmentTimeNeeded - charismaFactor)); } Bladeburner.prototype.getRecruitmentSuccessChance = function() { @@ -1613,13 +1046,13 @@ Bladeburner.prototype.gainActionStats = function(action, success) { * Gain multiplier based on difficulty. If this changes then the * same variable calculated in completeAction() needs to change too */ - var difficultyMult = Math.pow(difficulty, DiffMultExponentialFactor) + difficulty / DiffMultLinearFactor; + var difficultyMult = Math.pow(difficulty, BladeburnerConstants.DiffMultExponentialFactor) + difficulty / BladeburnerConstants.DiffMultLinearFactor; var time = this.actionTimeToComplete; var successMult = success ? 1 : 0.5; - var unweightedGain = time * BaseStatGain * successMult * difficultyMult; - var unweightedIntGain = time * BaseIntGain * successMult * difficultyMult; + var unweightedGain = time * BladeburnerConstants.BaseStatGain * successMult * difficultyMult; + var unweightedIntGain = time * BladeburnerConstants.BaseIntGain * successMult * difficultyMult; const skillMult = this.skillMultipliers.expGain; Player.gainHackingExp(unweightedGain * action.weights.hack * Player.hacking_exp_mult * skillMult); Player.gainStrengthExp(unweightedGain * action.weights.str * Player.strength_exp_mult * skillMult); @@ -1634,15 +1067,15 @@ Bladeburner.prototype.randomEvent = function() { var chance = Math.random(); // Choose random source/destination city for events - var sourceCityName = CityNames[getRandomInt(0, 5)]; + var sourceCityName = BladeburnerConstants.CityNames[getRandomInt(0, 5)]; var sourceCity = this.cities[sourceCityName]; if (!(sourceCity instanceof City)) { throw new Error("sourceCity was not a City object in Bladeburner.randomEvent()"); } - var destCityName = CityNames[getRandomInt(0, 5)]; + var destCityName = BladeburnerConstants.CityNames[getRandomInt(0, 5)]; while (destCityName === sourceCityName) { - destCityName = CityNames[getRandomInt(0, 5)]; + destCityName = BladeburnerConstants.CityNames[getRandomInt(0, 5)]; } var destCity = this.cities[destCityName]; @@ -1726,9 +1159,9 @@ Bladeburner.prototype.triggerPotentialMigration = function(sourceCityName, chanc } Bladeburner.prototype.triggerMigration = function(sourceCityName) { - var destCityName = CityNames[getRandomInt(0, 5)]; + var destCityName = BladeburnerConstants.CityNames[getRandomInt(0, 5)]; while (destCityName === sourceCityName) { - destCityName = CityNames[getRandomInt(0, 5)]; + destCityName = BladeburnerConstants.CityNames[getRandomInt(0, 5)]; } var destCity = this.cities[destCityName]; var sourceCity = this.cities[sourceCityName]; @@ -2009,7 +1442,7 @@ Bladeburner.prototype.createOverviewContent = function() { "in for your Bladeburner duties does not affect " + "your location in the game otherwise", })); - for (var i = 0; i < CityNames.length; ++i) { + for (var i = 0; i < BladeburnerConstants.CityNames.length; ++i) { (function(inst, i) { popupArguments.push(createElement("div", { /** @@ -2017,9 +1450,9 @@ Bladeburner.prototype.createOverviewContent = function() { * so that background color changes when you hover */ class:"cmpy-mgmt-find-employee-option", - innerText:CityNames[i], + innerText:BladeburnerConstants.CityNames[i], clickListener:()=>{ - inst.city = CityNames[i]; + inst.city = BladeburnerConstants.CityNames[i]; removeElementById(popupId); inst.updateOverviewContent(); return false; @@ -2046,7 +1479,7 @@ Bladeburner.prototype.createOverviewContent = function() { Engine.loadFactionContent(); displayFactionContent(bladeburnersFactionName); } else { - if (this.rank >= RankNeededForFaction) { + if (this.rank >= BladeburnerConstants.RankNeededForFaction) { joinFaction(bladeburnerFac); dialogBoxCreate("Congratulations! You were accepted into the Bladeburners faction"); removeChildrenFromElement(DomElems.overviewDiv); @@ -2200,10 +1633,12 @@ Bladeburner.prototype.createOperationsContent = function() { } Bladeburner.prototype.createBlackOpsContent = function() { + if (DomElems.actionsAndSkillsList == null || DomElems.actionsAndSkillsDesc == null) { throw new Error("Bladeburner.createBlackOpsContent called with either " + "DomElems.actionsAndSkillsList or DomElems.actionsAndSkillsDesc = null"); } + DomElems.actionsAndSkillsDesc.innerHTML = "Black Operations (Black Ops) are special, one-time covert operations. " + @@ -2242,7 +1677,7 @@ Bladeburner.prototype.createSkillsContent = function() { // Display Current multipliers DomElems.actionsAndSkillsDesc.innerHTML = - "You will gain one skill point every " + RanksPerSkillPoint + " ranks.

" + + "You will gain one skill point every " + BladeburnerConstants.RanksPerSkillPoint + " ranks.

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

" var multKeys = Object.keys(this.skillMultipliers); @@ -2302,12 +1737,6 @@ Bladeburner.prototype.createSkillsContent = function() { case "expGain": DomElems.actionsAndSkillsDesc.innerHTML += "Exp Gain: x" + mult + "
"; break; - case "weaponAbility": - // TODO in the future if items are ever implemented - break; - case "gunAbility": - // TODO in the future if items are ever implemented - break; default: console.warn(`Unrecognized SkillMult Key: ${multKeys[i]}`); break; @@ -2353,7 +1782,7 @@ Bladeburner.prototype.updateOverviewContent = function() { DomElems.overviewEstComms.childNodes[0].nodeValue = "Est. Synthoid Communities: " + formatNumber(this.getCurrentCity().comms, 0); DomElems.overviewChaos.childNodes[0].nodeValue = "City Chaos: " + formatNumber(this.getCurrentCity().chaos); DomElems.overviewSkillPoints.innerText = "Skill Points: " + formatNumber(this.skillPoints, 0); - DomElems.overviewBonusTime.childNodes[0].nodeValue = "Bonus time: " + convertTimeMsToTimeElapsedString(this.storedCycles/CyclesPerSecond*1000); + DomElems.overviewBonusTime.childNodes[0].nodeValue = "Bonus time: " + convertTimeMsToTimeElapsedString(this.storedCycles/BladeburnerConstants.CyclesPerSecond*1000); DomElems.overviewAugSuccessMult.innerText = "Aug. Success Chance Mult: " + formatNumber(Player.bladeburner_success_chance_mult*100, 1) + "%"; DomElems.overviewAugMaxStaminaMult.innerText = "Aug. Max Stamina Mult: " + formatNumber(Player.bladeburner_max_stamina_mult*100, 1) + "%"; DomElems.overviewAugStaminaGainMult.innerText = "Aug. Stamina Gain Mult: " + formatNumber(Player.bladeburner_stamina_gain_mult*100, 1) + "%"; @@ -2530,7 +1959,7 @@ Bladeburner.prototype.updateContractsUIElement = function(el, action) { el.appendChild(createElement("pre", { display:"inline-block", innerText:"Level: " + action.level + " / " + action.maxLevel, - tooltip:action.getSuccessesNeededForNextLevel(ContractSuccessesPerLevel) + " successes " + + tooltip:action.getSuccessesNeededForNextLevel(BladeburnerConstants.ContractSuccessesPerLevel) + " successes " + "needed for next level" })); el.appendChild(createElement("a", { @@ -2668,7 +2097,7 @@ Bladeburner.prototype.updateOperationsUIElement = function(el, action) { el.appendChild(createElement("pre", { display:"inline-block", innerText:"Level: " + action.level + " / " + action.maxLevel, - tooltip:action.getSuccessesNeededForNextLevel(OperationSuccessesPerLevel) + " successes " + + tooltip:action.getSuccessesNeededForNextLevel(BladeburnerConstants.OperationSuccessesPerLevel) + " successes " + "needed for next level" })); el.appendChild(createElement("a", { @@ -2928,8 +2357,8 @@ Bladeburner.prototype.executeConsoleCommands = function(commands) { } consoleHistoryIndex = this.consoleHistory.length; - var arrayOfCommands = commands.split(";"); - for (var i = 0; i < arrayOfCommands.length; ++i) { + const arrayOfCommands = commands.split(";"); + for (let i = 0; i < arrayOfCommands.length; ++i) { this.executeConsoleCommand(arrayOfCommands[i]); } } catch(e) { @@ -3131,13 +2560,14 @@ Bladeburner.prototype.executeAutomateConsoleCommand = function(args) { Bladeburner.prototype.executeHelpConsoleCommand = function(args) { if (args.length === 1) { - this.postToConsole(consoleHelpText.helpList); + for(const line of ConsoleHelpText.helpList){ + this.postToConsole(line); + } } else { for (var i = 1; i < args.length; ++i) { - var commandText = consoleHelpText[args[i]]; - if (commandText != null) { - this.postToConsole(commandText); - this.postToConsole("
"); + const helpText = ConsoleHelpText[args[i]]; + for(const line of helpText){ + this.postToConsole(line); } } } @@ -3269,12 +2699,6 @@ Bladeburner.prototype.executeSkillConsoleCommand = function(args) { case "stamina": this.postToConsole("Stamina: x" + mult); break; - case "weaponAbility": - // TODO if items are ever implemented - break; - case "gunAbility": - // TODO if items are ever implemented - break; default: console.warn(`Unrecognized SkillMult Key: ${multKeys[i]}`); break; @@ -3293,7 +2717,11 @@ Bladeburner.prototype.executeSkillConsoleCommand = function(args) { return this.postToConsole("Invalid skill name (Note that this is case-sensitive): " + skillName); } if (args[1].toLowerCase() === "list") { - this.postToConsole(skill.name + ": Level " + formatNumber(this.skills[skill.name]), 0); + let level = 0; + if (this.skills[skill.name] !== undefined) { + level = this.skills[skill.name]; + } + this.postToConsole(skill.name + ": Level " + formatNumber(level), 0); } else if (args[1].toLowerCase() === "level") { var currentLevel = 0; if (this.skills[skillName] && !isNaN(this.skills[skillName])) { @@ -3781,7 +3209,7 @@ Bladeburner.prototype.joinBladeburnerFactionNetscriptFn = function(workerScript) var bladeburnerFac = Factions["Bladeburners"]; if (bladeburnerFac.isMember) { return true; - } else if (this.rank >= RankNeededForFaction) { + } else if (this.rank >= BladeburnerConstants.RankNeededForFaction) { joinFaction(bladeburnerFac); workerScript.log("bladeburner.joinBladeburnerFaction", "Joined Bladeburners faction."); if (routing.isOn(Page.Bladeburner)) { @@ -3790,7 +3218,7 @@ Bladeburner.prototype.joinBladeburnerFactionNetscriptFn = function(workerScript) } return true; } else { - workerScript.log("bladeburner.joinBladeburnerFaction", `You do not have the required rank (${this.rank}/${RankNeededForFaction}).`); + workerScript.log("bladeburner.joinBladeburnerFaction", `You do not have the required rank (${this.rank}/${BladeburnerConstants.RankNeededForFaction}).`); return false; } } @@ -3803,483 +3231,4 @@ Bladeburner.fromJSON = function(value) { } Reviver.constructors.Bladeburner = Bladeburner; -/** - * This initialized Bladeburner-related data that is NOT saved/loaded - * eg: Skill Objects, BLack Operations - * Any data that is saved/loaded should go in Bladeburner object - * eg: contracts, operations - */ -function initBladeburner() { - // Skills - Skills[SkillNames.BladesIntuition] = new Skill({ - name:SkillNames.BladesIntuition, - desc:"Each level of this skill increases your success chance " + - "for all Contracts, Operations, and BlackOps by 3%", - baseCost: 3, costInc: 2.1, - successChanceAll:3 - }); - Skills[SkillNames.Cloak] = new Skill({ - name:SkillNames.Cloak, - desc:"Each level of this skill increases your " + - "success chance in stealth-related Contracts, Operations, and BlackOps by 5.5%", - baseCost: 2, costInc: 1.1, - successChanceStealth:5.5 - }); - - // TODO Marksman - If items are ever implemented - // TODO Weapon Proficiency - If items are ever implemented - - Skills[SkillNames.ShortCircuit] = new Skill({ - name:SkillNames.ShortCircuit, - desc:"Each level of this skill increases your success chance " + - "in Contracts, Operations, and BlackOps that involve retirement by 5.5%", - baseCost: 2, costInc: 2.1, - successChanceKill:5.5 - }); - Skills[SkillNames.DigitalObserver] = new Skill({ - name:SkillNames.DigitalObserver, - desc:"Each level of this skill increases your success chance in " + - "all Operations and BlackOps by 4%", - baseCost: 2, costInc: 2.1, - successChanceOperation:4 - }); - Skills[SkillNames.Tracer] = new Skill({ - name:SkillNames.Tracer, - desc:"Each level of this skill increases your success chance in " + - "all Contracts by 4%", - baseCost: 2, costInc: 2.1, - successChanceContract:4 - }); - Skills[SkillNames.Overclock] = new Skill({ - name:SkillNames.Overclock, - desc:"Each level of this skill decreases the time it takes " + - "to attempt a Contract, Operation, and BlackOp by 1% (Max Level: 90)", - baseCost: 3, costInc: 1.4, maxLvl: 90, - actionTime:1 - }); - Skills[SkillNames.Reaper] = new Skill({ - name: SkillNames.Reaper, - desc: "Each level of this skill increases your effective combat stats for Bladeburner actions by 2%", - baseCost: 2, costInc: 2.1, - effStr: 2, effDef: 2, effDex: 2, effAgi: 2 - }); - Skills[SkillNames.EvasiveSystem] = new Skill({ - name:SkillNames.EvasiveSystem, - desc:"Each level of this skill increases your effective " + - "dexterity and agility for Bladeburner actions by 4%", - baseCost: 2, costInc: 2.1, - effDex: 4, effAgi: 4 - }); - Skills[SkillNames.Datamancer] = new Skill({ - name:SkillNames.Datamancer, - desc:"Each level of this skill increases your effectiveness in " + - "synthoid population analysis and investigation by 5%. " + - "This affects all actions that can potentially increase " + - "the accuracy of your synthoid population/community estimates.", - baseCost:3, costInc:1, - successChanceEstimate:5 - }); - Skills[SkillNames.CybersEdge] = new Skill({ - name:SkillNames.CybersEdge, - desc:"Each level of this skill increases your max stamina by 2%", - baseCost:1, costInc:3, - stamina:2 - }); - Skills[SkillNames.HandsOfMidas] = new Skill({ - name: SkillNames.HandsOfMidas, - desc: "Each level of this skill increases the amount of money you receive from Contracts by 10%", - baseCost: 2, costInc: 2.5, - money: 10, - }); - Skills[SkillNames.Hyperdrive] = new Skill({ - name: SkillNames.Hyperdrive, - desc: "Each level of this skill increases the experience earned from Contracts, Operations, and BlackOps by 10%", - baseCost: 1, costInc: 2.5, - expGain: 10, - }); - - // General Actions - let actionName; - actionName = "Training"; - GeneralActions[actionName] = new Action({ - name:actionName, - desc:"Improve your abilities at the Bladeburner unit's specialized training " + - "center. Doing this gives experience for all combat stats and also " + - "increases your max stamina." - }); - - actionName = "Field Analysis"; - GeneralActions[actionName] = new Action({ - name:actionName, - desc:"Mine and analyze Synthoid-related data. This improve the " + - "Bladeburner's unit intelligence on Synthoid locations and " + - "activities. Completing this action will improve the accuracy " + - "of your Synthoid population estimated in the current city.

" + - "Does NOT require stamina." - }); - - actionName = "Recruitment"; - GeneralActions[actionName] = new Action({ - name:actionName, - desc:"Attempt to recruit members for your Bladeburner team. These members " + - "can help you conduct operations.

" + - "Does NOT require stamina." - }); - - actionName = "Diplomacy"; - GeneralActions[actionName] = new Action({ - name: actionName, - desc: "Improve diplomatic relations with the Synthoid population. " + - "Completing this action will reduce the Chaos level in your current city.

" + - "Does NOT require stamina." - }); - - actionName = "Hyperbolic Regeneration Chamber"; - GeneralActions[actionName] = new Action({ - name: actionName, - desc: "Enter cryogenic stasis using the Bladeburner division's hi-tech Regeneration Chamber. " + - "This will slowly heal your wounds and slightly increase your stamina.

", - }); - - // Black Operations - BlackOperations["Operation Typhoon"] = new BlackOperation({ - name:"Operation Typhoon", - desc:"Obadiah Zenyatta is the leader of a RedWater PMC. It has long " + - "been known among the intelligence community that Zenyatta, along " + - "with the rest of the PMC, is a Synthoid.

" + - "The goal of Operation Typhoon is to find and eliminate " + - "Zenyatta and RedWater by any means necessary. After the task " + - "is completed, the actions must be covered up from the general public.", - baseDifficulty:2000, reqdRank:2.5e3, - rankGain:50, rankLoss:10, hpLoss:100, - weights:{hack:0.1,str:0.2,def:0.2,dex:0.2,agi:0.2,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.75}, - isKill:true - }); - BlackOperations["Operation Zero"] = new BlackOperation({ - name:"Operation Zero", - desc:"AeroCorp is one of the world's largest defense contractors. " + - "It's leader, Steve Watataki, is thought to be a supporter of " + - "Synthoid rights. He must be removed.

" + - "The goal of Operation Zero is to covertly infiltrate AeroCorp and " + - "uncover any incriminating evidence or " + - "information against Watataki that will cause him to be removed " + - "from his position at AeroCorp. Incriminating evidence can be " + - "fabricated as a last resort. Be warned that AeroCorp has some of " + - "the most advanced security measures in the world.", - baseDifficulty:2500, reqdRank:5e3, - rankGain:60, rankLoss:15, hpLoss:50, - weights:{hack:0.2,str:0.15,def:0.15,dex:0.2,agi:0.2,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.75}, - isStealth:true - }); - BlackOperations["Operation X"] = new BlackOperation({ - name:"Operation X", - desc:"We have recently discovered an underground publication " + - "group called Samizdat. Even though most of their publications " + - "are nonsensical conspiracy theories, the average human is " + - "gullible enough to believe them. Many of their works discuss " + - "Synthoids and pose a threat to society. The publications are spreading " + - "rapidly in China and other Eastern countries.

" + - "Samizdat has done a good job of keeping hidden and anonymous. " + - "However, we've just received intelligence that their base of " + - "operations is in Ishima's underground sewer systems. Your task is to " + - "investigate the sewer systems, and eliminate Samizdat. They must " + - "never publish anything again.", - baseDifficulty:3000, reqdRank:7.5e3, - rankGain:75, rankLoss:15, hpLoss:100, - weights:{hack:0.1,str:0.2,def:0.2,dex:0.2,agi:0.2,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.75}, - isKill:true - }); - BlackOperations["Operation Titan"] = new BlackOperation({ - name:"Operation Titan", - desc:"Several months ago Titan Laboratories' Bioengineering department " + - "was infiltrated by Synthoids. As far as we know, Titan Laboratories' " + - "management has no knowledge about this. We don't know what the " + - "Synthoids are up to, but the research that they could " + - "be conducting using Titan Laboraties' vast resources is potentially " + - "very dangerous.

" + - "Your goal is to enter and destroy the Bioengineering department's " + - "facility in Aevum. The task is not just to retire the Synthoids there, but " + - "also to destroy any information or research at the facility that " + - "is relevant to the Synthoids and their goals.", - baseDifficulty:4000, reqdRank:10e3, - rankGain:100, rankLoss:20, hpLoss:100, - weights:{hack:0.1,str:0.2,def:0.2,dex:0.2,agi:0.2,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.75}, - isKill:true - }); - BlackOperations["Operation Ares"] = new BlackOperation({ - name:"Operation Ares", - desc:"One of our undercover agents, Agent Carter, has informed us of a " + - "massive weapons deal going down in Dubai between rogue Russian " + - "militants and a radical Synthoid community. These weapons are next-gen " + - "plasma and energy weapons. It is critical for the safety of humanity " + - "that this deal does not happen.

" + - "Your task is to intercept the deal. Leave no survivors.", - baseDifficulty:5000, reqdRank:12.5e3, - rankGain:125, rankLoss:20, hpLoss:200, - weights:{hack:0,str:0.25,def:0.25,dex:0.25,agi:0.25,cha:0, int:0}, - decays:{hack:0,str:0.8,def:0.8,dex:0.8,agi:0.8,cha:0, int:0.75}, - isKill:true - }); - BlackOperations["Operation Archangel"] = new BlackOperation({ - name:"Operation Archangel", - desc:"Our analysts have discovered that the popular Red Rabbit brothel in " + - "Amsterdam is run and 'staffed' by MK-VI Synthoids. Intelligence " + - "suggests that the profit from this brothel is used to fund a large " + - "black market arms trafficking operation.

" + - "The goal of this operation is to take out the leaders that are running " + - "the Red Rabbit brothel. Try to limit the number of other casualties, " + - "but do what you must to complete the mission.", - baseDifficulty:7500, reqdRank:15e3, - rankGain:200, rankLoss:20, hpLoss:25, - weights:{hack:0,str:0.2,def:0.2,dex:0.3,agi:0.3,cha:0, int:0}, - decays:{hack:0,str:0.8,def:0.8,dex:0.8,agi:0.8,cha:0, int:0.75}, - isKill:true, - }); - BlackOperations["Operation Juggernaut"] = new BlackOperation({ - name:"Operation Juggernaut", - desc:"The CIA has just encountered a new security threat. A new " + - "criminal group, lead by a shadowy operative who calls himself " + - "Juggernaut, has been smuggling drugs and weapons (including " + - "suspected bioweapons) into Sector-12. We also have reason " + - "to believe the tried to break into one of Universal Energy's " + - "facilities in order to cause a city-wide blackout. The CIA " + - "suspects that Juggernaut is a heavily-augmented Synthoid, and " + - "have thus enlisted our help.

" + - "Your mission is to eradicate Juggernaut and his followers.", - baseDifficulty:10e3, reqdRank:20e3, - rankGain:300, rankLoss:40, hpLoss:300, - weights:{hack:0,str:0.25,def:0.25,dex:0.25,agi:0.25,cha:0, int:0}, - decays:{hack:0,str:0.8,def:0.8,dex:0.8,agi:0.8,cha:0, int:0.75}, - isKill:true, - }); - BlackOperations["Operation Red Dragon"] = new BlackOperation({ - name:"Operation Red Dragon", - desc:"The Tetrads criminal organization is suspected of " + - "reverse-engineering the MK-VI Synthoid design. We believe " + - "they altered and possibly improved the design and began " + - "manufacturing their own Synthoid models in order to bolster " + - "their criminal activities.

" + - "Your task is to infiltrate and destroy the Tetrads' base of operations " + - "in Los Angeles. Intelligence tells us that their base houses " + - "one of their Synthoid manufacturing units.", - baseDifficulty:12.5e3, reqdRank:25e3, - rankGain:500, rankLoss:50, hpLoss:500, - weights:{hack:0.05,str:0.2,def:0.2,dex:0.25,agi:0.25,cha:0, int:0.05}, - decays:{hack:0.6,str:0.8,def:0.8,dex:0.8,agi:0.8,cha:0, int:0.75}, - isKill:true, - }); - BlackOperations["Operation K"] = new BlackOperation({ - name:"Operation K", - desc:"CODE RED SITUATION. Our intelligence tells us that VitaLife " + - "has discovered a new android cloning technology. This technology " + - "is supposedly capable of cloning Synthoid, not only physically " + - "but also their advanced AI modules. We do not believe that " + - "VitaLife is trying to use this technology illegally or " + - "maliciously, but if any Synthoids were able to infiltrate the " + - "corporation and take advantage of this technology then the " + - "results would be catastrophic.

" + - "We do not have the power or jurisdiction to shutdown this down " + - "through legal or political means, so we must resort to a covert " + - "operation. Your goal is to destroy this technology and eliminate " + - "anyone who was involved in its creation.", - baseDifficulty:15e3, reqdRank:30e3, - rankGain:750, rankLoss:60, hpLoss:1000, - weights:{hack:0.05,str:0.2,def:0.2,dex:0.25,agi:0.25,cha:0, int:0.05}, - decays:{hack:0.6,str:0.8,def:0.8,dex:0.8,agi:0.8,cha:0, int:0.75}, - isKill:true - }); - BlackOperations["Operation Deckard"] = new BlackOperation({ - name:"Operation Deckard", - desc:"Despite your success in eliminating VitaLife's new android-replicating " + - "technology in Operation K, we've discovered that a small group of " + - "MK-VI Synthoids were able to make off with the schematics and design " + - "of the technology before the Operation. It is almost a certainty that " + - "these Synthoids are some of the rogue MK-VI ones from the Synthoid Uprising." + - "The goal of Operation Deckard is to hunt down these Synthoids and retire " + - "them. I don't need to tell you how critical this mission is.", - baseDifficulty:20e3, reqdRank:40e3, - rankGain:1e3, rankLoss:75, hpLoss:200, - weights:{hack:0,str:0.24,def:0.24,dex:0.24,agi:0.24,cha:0, int:0.04}, - decays:{hack:0,str:0.8,def:0.8,dex:0.8,agi:0.8,cha:0, int:0.75}, - isKill:true, - }); - BlackOperations["Operation Tyrell"] = new BlackOperation({ - name:"Operation Tyrell", - desc:"A week ago Blade Industries reported a small break-in at one " + - "of their Aevum Augmentation storage facitilities. We figured out " + - "that The Dark Army was behind the heist, and didn't think any more " + - "of it. However, we've just discovered that several known MK-VI Synthoids " + - "were part of that break-in group.

" + - "We cannot have Synthoids upgrading their already-enhanced abilities " + - "with Augmentations. Your task is to hunt down the associated Dark Army " + - "members and eliminate them.", - baseDifficulty:25e3, reqdRank:50e3, - rankGain:1.5e3, rankLoss:100, hpLoss:500, - weights:{hack:0.1,str:0.2,def:0.2,dex:0.2,agi:0.2,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.75}, - isKill:true, - }); - BlackOperations["Operation Wallace"] = new BlackOperation({ - name:"Operation Wallace", - desc:"Based on information gathered from Operation Tyrell, we've discovered " + - "that The Dark Army was well aware that there were Synthoids amongst " + - "their ranks. Even worse, we believe that The Dark Army is working " + - "together with other criminal organizations such as The Syndicate and " + - "that they are planning some sort of large-scale takeover of multiple major " + - "cities, most notably Aevum. We suspect that Synthoids have infiltrated " + - "the ranks of these criminal factions and are trying to stage another " + - "Synthoid uprising.

" + - "The best way to deal with this is to prevent it before it even happens. " + - "The goal of Operation Wallace is to destroy the Dark Army and " + - "Syndicate factions in Aevum immediately. Leave no survivors.", - baseDifficulty:30e3, reqdRank:75e3, - rankGain:2e3, rankLoss:150, hpLoss:1500, - weights:{hack:0,str:0.24,def:0.24,dex:0.24,agi:0.24,cha:0, int:0.04}, - decays:{hack:0.6,str:0.8,def:0.8,dex:0.8,agi:0.8,cha:0, int:0.75}, - isKill:true - }); - BlackOperations["Operation Shoulder of Orion"] = new BlackOperation({ - name:"Operation Shoulder of Orion", - desc:"China's Solaris Space Systems is secretly launching the first " + - "manned spacecraft in over a decade using Synthoids. We believe " + - "China is trying to establish the first off-world colonies.

" + - "The mission is to prevent this launch without instigating an " + - "international conflict. When you accept this mission you will be " + - "officially disavowed by the NSA and the national government until after you " + - "successfully return. In the event of failure, all of the operation's " + - "team members must not let themselves be captured alive.", - baseDifficulty:35e3, reqdRank:100e3, - rankGain:2.5e3, rankLoss:500, hpLoss:1500, - weights:{hack:0.1,str:0.2,def:0.2,dex:0.2,agi:0.2,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.75}, - isStealth:true - }); - BlackOperations["Operation Hyron"] = new BlackOperation({ - name:"Operation Hyron", - desc:"Our intelligence tells us that Fulcrum Technologies is developing " + - "a quantum supercomputer using human brains as core " + - "processors. This supercomputer " + - "is rumored to be able to store vast amounts of data and " + - "perform computations unmatched by any other supercomputer on the " + - "planet. But more importantly, the use of organic human brains " + - "means that the supercomputer may be able to reason abstractly " + - "and become self-aware.

" + - "I do not need to remind you why sentient-level AIs pose a serious " + - "thread to all of mankind.

" + - "The research for this project is being conducted at one of Fulcrum " + - "Technologies secret facilities in Aevum, codenamed 'Alpha Ranch'. " + - "Infiltrate the compound, delete and destroy the work, and then find and kill the " + - "project lead.", - baseDifficulty:40e3, reqdRank:125e3, - rankGain:3e3, rankLoss:1e3, hpLoss:500, - weights:{hack:0.1,str:0.2,def:0.2,dex:0.2,agi:0.2,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.75}, - isKill:true - }); - BlackOperations["Operation Morpheus"] = new BlackOperation({ - name:"Operation Morpheus", - desc:"DreamSense Technologies is an advertising company that uses " + - "special technology to transmit their ads into the peoples " + - "dreams and subconcious. They do this using broadcast transmitter " + - "towers. Based on information from our agents and informants in " + - "Chonqging, we have reason to believe that one of the broadcast " + - "towers there has been compromised by Synthoids and is being used " + - "to spread pro-Synthoid propaganda.

" + - "The mission is to destroy this broadcast tower. Speed and " + - "stealth are of the upmost important for this.", - baseDifficulty:45e3, reqdRank:150e3, - rankGain:4e3, rankLoss:1e3, hpLoss:100, - weights:{hack:0.05,str:0.15,def:0.15,dex:0.3,agi:0.3,cha:0, int:0.05}, - decays:{hack:0.6,str:0.8,def:0.8,dex:0.8,agi:0.8,cha:0, int:0.75}, - isStealth:true - }); - BlackOperations["Operation Ion Storm"] = new BlackOperation({ - name:"Operation Ion Storm", - desc:"Our analysts have uncovered a gathering of MK-VI Synthoids " + - "that have taken up residence in the Sector-12 Slums. We " + - "don't know if they are rogue Synthoids from the Uprising, " + - "but we do know that they have been stockpiling " + - "weapons, money, and other resources. This makes them dangerous.

" + - "This is a full-scale assault operation to find and retire all of these " + - "Synthoids in the Sector-12 Slums.", - baseDifficulty:50e3, reqdRank:175e3, - rankGain:5e3, rankLoss:1e3, hpLoss:5000, - weights:{hack:0,str:0.24,def:0.24,dex:0.24,agi:0.24,cha:0, int:0.04}, - decays:{hack:0.6,str:0.8,def:0.8,dex:0.8,agi:0.8,cha:0, int:0.75}, - isKill:true - }); - BlackOperations["Operation Annihilus"] = new BlackOperation({ - name:"Operation Annihilus", - desc:"Our superiors have ordered us to eradicate everything and everyone " + - "in an underground facility located in Aevum. They tell us " + - "that the facility houses many dangerous Synthoids and " + - "belongs to a terrorist organization called " + - "'The Covenant'. We have no prior intelligence about this " + - "organization, so you are going in blind.", - baseDifficulty:55e3, reqdRank:200e3, - rankGain:7.5e3, rankLoss:1e3, hpLoss:10e3, - weights:{hack:0,str:0.24,def:0.24,dex:0.24,agi:0.24,cha:0, int:0.04}, - decays:{hack:0.6,str:0.8,def:0.8,dex:0.8,agi:0.8,cha:0, int:0.75}, - isKill:true - }); - BlackOperations["Operation Ultron"] = new BlackOperation({ - name:"Operation Ultron", - desc:"OmniTek Incorporated, the original designer and manufacturer of Synthoids, " + - "has notified us of a malfunction in their AI design. This malfunction, " + - "when triggered, causes MK-VI Synthoids to become radicalized and seek out " + - "the destruction of humanity. They say that this bug affects all MK-VI Synthoids, " + - "not just the rogue ones from the Uprising.

" + - "OmniTek has also told us they they believe someone has triggered this " + - "malfunction in a large group of MK-VI Synthoids, and that these newly-radicalized Synthoids " + - "are now amassing in Volhaven to form a terrorist group called Ultron.

" + - "Intelligence suggests Ultron is heavily armed and that their members are " + - "augmented. We believe Ultron is making moves to take control of " + - "and weaponize DeltaOne's Tactical High-Energy Satellite Laser Array (THESLA).

" + - "Your task is to find and destroy Ultron.", - baseDifficulty:60e3, reqdRank:250e3, - rankGain:10e3, rankLoss:2e3, hpLoss:10e3, - weights:{hack:0.1,str:0.2,def:0.2,dex:0.2,agi:0.2,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.75}, - isKill:true - }); - BlackOperations["Operation Centurion"] = new BlackOperation({ - name:"Operation Centurion", - desc:"D)@#)($M)C0293c40($*)@#D0JUMP3Rm0C<*@#)*$)#02c94830c(#$*D)

" + - "Throughout all of humanity's history, we have relied on " + - "technology to survive, conquer, and progress. Its advancement became our primary goal. " + - "And at the peak of human civilization technology turned into " + - "power. Global, absolute power.

" + - "It seems that the universe is not without a sense of irony.

" + - "D)@#)($M)C0293c40($*)@#D0JUMP3Rm0C<*@#)*$)#02c94830c(#$*D)", - baseDifficulty:70e3, reqdRank:300e3, - rankGain:15e3, rankLoss:5e3, hpLoss:10e3, - weights:{hack:0.1,str:0.2,def:0.2,dex:0.2,agi:0.2,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.75}, - }); - BlackOperations["Operation Vindictus"] = new BlackOperation({ - name:"Operation Vindictus", - desc:"D)@#)($M)C0293c40($*)@#D0JUMP3Rm0C<*@#)*$)#02c94830c(#$*D)

" + - "The bits are all around us. The daemons that hold the Node " + - "together can manifest themselves in many different ways.

" + - "D)@#)($M)C0293c40($*)@#D0JUMP3Rm0C<*@#)*$)#02c94830c(#$*D)", - baseDifficulty:75e3, reqdRank:350e3, - rankGain:20e3, rankLoss:20e3, hpLoss:20e3, - weights:{hack:0.1,str:0.2,def:0.2,dex:0.2,agi:0.2,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.75}, - }); - BlackOperations["Operation Daedalus"] = new BlackOperation({ - name:"Operation Daedalus", - desc:"Yesterday we obeyed kings and bent our neck to emperors. " + - "Today we kneel only to truth.", - baseDifficulty:80e3, reqdRank:400e3, - rankGain:40e3, rankLoss:10e3, hpLoss:100e3, - weights:{hack:0.1,str:0.2,def:0.2,dex:0.2,agi:0.2,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.75}, - }); -} - export { Bladeburner }; diff --git a/src/Bladeburner/Action.ts b/src/Bladeburner/Action.ts new file mode 100644 index 000000000..64ea44627 --- /dev/null +++ b/src/Bladeburner/Action.ts @@ -0,0 +1,269 @@ +import { Player } from "../Player"; +import { getRandomInt } from "../../utils/helpers/getRandomInt"; +import { addOffset } from "../../utils/helpers/addOffset"; +import { Generic_fromJSON, Generic_toJSON, Reviver } from "../../utils/JSONReviver"; +import { BladeburnerConstants } from "./data/Constants"; +// import { Contract } from "./Contract"; +// import { Operation } from "./Operation"; +// import { BlackOperation } from "./BlackOperation"; + +class StatsMultiplier { + hack: number = 0; + str: number = 0; + def: number = 0; + dex: number = 0; + agi: number = 0; + cha: number = 0; + int: number = 0; + + [key: string]: number; +}; + +export interface IActionParams { + name?: string; + desc?: string; + level?: number; + maxLevel?: number; + autoLevel?: boolean; + baseDifficulty?: number; + difficultyFac?: number; + rewardFac?: number; + successes?: number; + failures?: number; + rankGain?: number; + rankLoss?: number; + hpLoss?: number; + hpLost?: number; + isStealth?: boolean; + isKill?: boolean; + count?: number; + countGrowth?: number; + weights?: StatsMultiplier; + decays?: StatsMultiplier; + teamCount?: number; +} + +export class Action { + name: string = ""; + desc: string = ""; + + // Difficulty scales with level. See getDifficulty() method + level: number = 1; + maxLevel: number = 1; + autoLevel: boolean = true; + baseDifficulty: number = 100; + difficultyFac: number = 1.01; + + // Rank increase/decrease is affected by this exponent + rewardFac: number = 1.02; + + successes: number = 0; + failures: number = 0; + + // All of these scale with level/difficulty + rankGain: number = 0; + rankLoss: number = 0; + hpLoss: number = 0; + hpLost: number = 0; + + // Action Category. Current categories are stealth and kill + isStealth: boolean = false; + isKill: boolean = false; + + /** + * Number of this contract remaining, and its growth rate + * Growth rate is an integer and the count will increase by that integer every "cycle" + */ + count: number = getRandomInt(1e3, 25e3); + countGrowth: number = getRandomInt(1, 5); + + // Weighting of each stat in determining action success rate + weights: StatsMultiplier = {hack:1/7,str:1/7,def:1/7,dex:1/7,agi:1/7,cha:1/7,int:1/7}; + // Diminishing returns of stats (stat ^ decay where 0 <= decay <= 1) + decays: StatsMultiplier = { hack: 0.9, str: 0.9, def: 0.9, dex: 0.9, agi: 0.9, cha: 0.9, int: 0.9 }; + teamCount: number = 0; + + // Base Class for Contracts, Operations, and BlackOps + constructor(params: IActionParams| null = null) { // | null = null + if(params && params.name) this.name = params.name; + if(params && params.desc) this.desc = params.desc; + + if(params && params.baseDifficulty) this.baseDifficulty = addOffset(params.baseDifficulty, 10); + if(params && params.difficultyFac) this.difficultyFac = params.difficultyFac; + + if(params && params.rewardFac) this.rewardFac = params.rewardFac; + if(params && params.rankGain) this.rankGain = params.rankGain; + if(params && params.rankLoss) this.rankLoss = params.rankLoss; + if(params && params.hpLoss) this.hpLoss = params.hpLoss; + + if(params && params.isStealth) this.isStealth = params.isStealth; + if(params && params.isKill) this.isKill = params.isKill; + + if(params && params.count) this.count = params.count; + if(params && params.countGrowth) this.countGrowth = params.countGrowth; + + if(params && params.weights) this.weights = params.weights; + if(params && params.decays) this.decays = params.decays; + + // Check to make sure weights are summed properly + let sum = 0; + for (const weight in this.weights) { + if (this.weights.hasOwnProperty(weight)) { + sum += this.weights[weight]; + } + } + if (sum - 1 >= 10 * Number.EPSILON) { + throw new Error("Invalid weights when constructing Action " + this.name + + ". The weights should sum up to 1. They sum up to :" + 1); + } + + for (const decay in this.decays) { + if (this.decays.hasOwnProperty(decay)) { + if (this.decays[decay] > 1) { + throw new Error("Invalid decays when constructing " + + "Action " + this.name + ". " + + "Decay value cannot be greater than 1"); + } + } + } + } + + getDifficulty(): number { + const difficulty = this.baseDifficulty * Math.pow(this.difficultyFac, this.level-1); + if (isNaN(difficulty)) {throw new Error("Calculated NaN in Action.getDifficulty()");} + return difficulty; + } + + /** + * Tests for success. Should be called when an action has completed + * @param inst {Bladeburner} - Bladeburner instance + */ + attempt(inst: any): boolean { + return (Math.random() < this.getSuccessChance(inst)); + } + + // To be implemented by subtypes + getActionTimePenalty(): number { + return 1; + } + + getActionTime(inst: any): number { + const difficulty = this.getDifficulty(); + let baseTime = difficulty / BladeburnerConstants.DifficultyToTimeFactor; + const skillFac = inst.skillMultipliers.actionTime; // Always < 1 + + const effAgility = Player.agility * inst.skillMultipliers.effAgi; + const effDexterity = Player.dexterity * inst.skillMultipliers.effDex; + const statFac = 0.5 * (Math.pow(effAgility, BladeburnerConstants.EffAgiExponentialFactor) + + Math.pow(effDexterity, BladeburnerConstants.EffDexExponentialFactor) + + (effAgility / BladeburnerConstants.EffAgiLinearFactor) + + (effDexterity / BladeburnerConstants.EffDexLinearFactor)); // Always > 1 + + baseTime = Math.max(1, baseTime * skillFac / statFac); + + return Math.ceil(baseTime*this.getActionTimePenalty()); + } + + // For actions that have teams. To be implemented by subtypes. + getTeamSuccessBonus(inst: any): number { + return 1; + } + + getActionTypeSkillSuccessBonus(inst: any): number { + return 1; + } + + getChaosCompetencePenalty(inst: any, params: any): number { + const city = inst.getCurrentCity(); + if (params.est) { + return Math.pow((city.popEst / BladeburnerConstants.PopulationThreshold), BladeburnerConstants.PopulationExponent); + } else { + return Math.pow((city.pop / BladeburnerConstants.PopulationThreshold), BladeburnerConstants.PopulationExponent); + } + } + + getChaosDifficultyBonus(inst: any, params: any): number { + const city = inst.getCurrentCity(); + if (city.chaos > BladeburnerConstants.ChaosThreshold) { + const diff = 1 + (city.chaos - BladeburnerConstants.ChaosThreshold); + const mult = Math.pow(diff, 0.1); + return mult; + } + + return 1; + } + + /** + * @inst - Bladeburner Object + * @params - options: + * est (bool): Get success chance estimate instead of real success chance + */ + getSuccessChance(inst: any, params: any={}) { + if (inst == null) {throw new Error("Invalid Bladeburner instance passed into Action.getSuccessChance");} + let difficulty = this.getDifficulty(); + let competence = 0; + for (let stat in this.weights) { + if (this.weights.hasOwnProperty(stat)) { + let playerStatLvl = Player.queryStatFromString(stat); + let key = "eff" + stat.charAt(0).toUpperCase() + stat.slice(1); + let effMultiplier = inst.skillMultipliers[key]; + if (effMultiplier == null) { + console.error(`Failed to find Bladeburner Skill multiplier for: ${stat}`); + effMultiplier = 1; + } + competence += (this.weights[stat] * Math.pow(effMultiplier*playerStatLvl, this.decays[stat])); + } + } + competence *= inst.calculateStaminaPenalty(); + + competence *= this.getTeamSuccessBonus(inst); + + competence *= this.getChaosCompetencePenalty(inst, params); + difficulty *= this.getChaosDifficultyBonus(inst, params); + + if(this.name == "Raid" && inst.getCurrentCity().comms <= 0) { + return 0; + } + + // Factor skill multipliers into success chance + competence *= inst.skillMultipliers.successChanceAll; + competence *= this.getActionTypeSkillSuccessBonus(inst); + if (this.isStealth) { + competence *= inst.skillMultipliers.successChanceStealth; + } + if (this.isKill) { + competence *= inst.skillMultipliers.successChanceKill; + } + + // Augmentation multiplier + competence *= Player.bladeburner_success_chance_mult; + + if (isNaN(competence)) {throw new Error("Competence calculated as NaN in Action.getSuccessChance()");} + return Math.min(1, competence / difficulty); + } + + getSuccessesNeededForNextLevel(baseSuccessesPerLevel: number): number { + return Math.ceil((0.5) * (this.maxLevel) * (2 * baseSuccessesPerLevel + (this.maxLevel-1))); + } + + setMaxLevel(baseSuccessesPerLevel: number): void { + if (this.successes >= this.getSuccessesNeededForNextLevel(baseSuccessesPerLevel)) { + ++this.maxLevel; + } + } + + static fromJSON(value: any): Action { + return Generic_fromJSON(Action, value.data); + } + + toJSON(): any { + return Generic_toJSON("Action", this); + } +} + + + + + + +Reviver.constructors.Action = Action; \ No newline at end of file diff --git a/src/Bladeburner/BlackOperation.ts b/src/Bladeburner/BlackOperation.ts new file mode 100644 index 000000000..11ac83dc4 --- /dev/null +++ b/src/Bladeburner/BlackOperation.ts @@ -0,0 +1,33 @@ +import { Operation, IOperationParams } from "./Operation"; +import { Generic_fromJSON, Generic_toJSON, Reviver } from "../../utils/JSONReviver"; + +export class BlackOperation extends Operation { + constructor(params: IOperationParams | null = null) { + super(params); + this.count = 1; + this.countGrowth = 0; + } + + // To be implemented by subtypes + getActionTimePenalty(): number { + return 1.5; + } + + getChaosCompetencePenalty(inst: any, params: any): number { + return 1; + } + + getChaosDifficultyBonus(inst: any, params: any): number { + return 1; + } + + static fromJSON(value: any): Operation { + return Generic_fromJSON(BlackOperation, value.data); + } + + toJSON(): any { + return Generic_toJSON("BlackOperation", this); + } +} + +Reviver.constructors.BlackOperation = BlackOperation; \ No newline at end of file diff --git a/src/Bladeburner/BlackOperations.ts b/src/Bladeburner/BlackOperations.ts new file mode 100644 index 000000000..75e6e799e --- /dev/null +++ b/src/Bladeburner/BlackOperations.ts @@ -0,0 +1,345 @@ +import { BlackOperation } from "./BlackOperation"; +import { IMap } from "../types"; + +export const BlackOperations: IMap = {}; + +(function() { + BlackOperations["Operation Typhoon"] = new BlackOperation({ + name:"Operation Typhoon", + desc:"Obadiah Zenyatta is the leader of a RedWater PMC. It has long " + + "been known among the intelligence community that Zenyatta, along " + + "with the rest of the PMC, is a Synthoid.

" + + "The goal of Operation Typhoon is to find and eliminate " + + "Zenyatta and RedWater by any means necessary. After the task " + + "is completed, the actions must be covered up from the general public.", + baseDifficulty:2000, reqdRank:2.5e3, + rankGain:50, rankLoss:10, hpLoss:100, + weights:{hack:0.1,str:0.2,def:0.2,dex:0.2,agi:0.2,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.75}, + isKill:true + }); + BlackOperations["Operation Zero"] = new BlackOperation({ + name:"Operation Zero", + desc:"AeroCorp is one of the world's largest defense contractors. " + + "It's leader, Steve Watataki, is thought to be a supporter of " + + "Synthoid rights. He must be removed.

" + + "The goal of Operation Zero is to covertly infiltrate AeroCorp and " + + "uncover any incriminating evidence or " + + "information against Watataki that will cause him to be removed " + + "from his position at AeroCorp. Incriminating evidence can be " + + "fabricated as a last resort. Be warned that AeroCorp has some of " + + "the most advanced security measures in the world.", + baseDifficulty:2500, reqdRank:5e3, + rankGain:60, rankLoss:15, hpLoss:50, + weights:{hack:0.2,str:0.15,def:0.15,dex:0.2,agi:0.2,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.75}, + isStealth:true + }); + BlackOperations["Operation X"] = new BlackOperation({ + name:"Operation X", + desc:"We have recently discovered an underground publication " + + "group called Samizdat. Even though most of their publications " + + "are nonsensical conspiracy theories, the average human is " + + "gullible enough to believe them. Many of their works discuss " + + "Synthoids and pose a threat to society. The publications are spreading " + + "rapidly in China and other Eastern countries.

" + + "Samizdat has done a good job of keeping hidden and anonymous. " + + "However, we've just received intelligence that their base of " + + "operations is in Ishima's underground sewer systems. Your task is to " + + "investigate the sewer systems, and eliminate Samizdat. They must " + + "never publish anything again.", + baseDifficulty:3000, reqdRank:7.5e3, + rankGain:75, rankLoss:15, hpLoss:100, + weights:{hack:0.1,str:0.2,def:0.2,dex:0.2,agi:0.2,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.75}, + isKill:true + }); + BlackOperations["Operation Titan"] = new BlackOperation({ + name:"Operation Titan", + desc:"Several months ago Titan Laboratories' Bioengineering department " + + "was infiltrated by Synthoids. As far as we know, Titan Laboratories' " + + "management has no knowledge about this. We don't know what the " + + "Synthoids are up to, but the research that they could " + + "be conducting using Titan Laboraties' vast resources is potentially " + + "very dangerous.

" + + "Your goal is to enter and destroy the Bioengineering department's " + + "facility in Aevum. The task is not just to retire the Synthoids there, but " + + "also to destroy any information or research at the facility that " + + "is relevant to the Synthoids and their goals.", + baseDifficulty:4000, reqdRank:10e3, + rankGain:100, rankLoss:20, hpLoss:100, + weights:{hack:0.1,str:0.2,def:0.2,dex:0.2,agi:0.2,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.75}, + isKill:true + }); + BlackOperations["Operation Ares"] = new BlackOperation({ + name:"Operation Ares", + desc:"One of our undercover agents, Agent Carter, has informed us of a " + + "massive weapons deal going down in Dubai between rogue Russian " + + "militants and a radical Synthoid community. These weapons are next-gen " + + "plasma and energy weapons. It is critical for the safety of humanity " + + "that this deal does not happen.

" + + "Your task is to intercept the deal. Leave no survivors.", + baseDifficulty:5000, reqdRank:12.5e3, + rankGain:125, rankLoss:20, hpLoss:200, + weights:{hack:0,str:0.25,def:0.25,dex:0.25,agi:0.25,cha:0, int:0}, + decays:{hack:0,str:0.8,def:0.8,dex:0.8,agi:0.8,cha:0, int:0.75}, + isKill:true + }); + BlackOperations["Operation Archangel"] = new BlackOperation({ + name:"Operation Archangel", + desc:"Our analysts have discovered that the popular Red Rabbit brothel in " + + "Amsterdam is run and 'staffed' by MK-VI Synthoids. Intelligence " + + "suggests that the profit from this brothel is used to fund a large " + + "black market arms trafficking operation.

" + + "The goal of this operation is to take out the leaders that are running " + + "the Red Rabbit brothel. Try to limit the number of other casualties, " + + "but do what you must to complete the mission.", + baseDifficulty:7500, reqdRank:15e3, + rankGain:200, rankLoss:20, hpLoss:25, + weights:{hack:0,str:0.2,def:0.2,dex:0.3,agi:0.3,cha:0, int:0}, + decays:{hack:0,str:0.8,def:0.8,dex:0.8,agi:0.8,cha:0, int:0.75}, + isKill:true, + }); + BlackOperations["Operation Juggernaut"] = new BlackOperation({ + name:"Operation Juggernaut", + desc:"The CIA has just encountered a new security threat. A new " + + "criminal group, lead by a shadowy operative who calls himself " + + "Juggernaut, has been smuggling drugs and weapons (including " + + "suspected bioweapons) into Sector-12. We also have reason " + + "to believe the tried to break into one of Universal Energy's " + + "facilities in order to cause a city-wide blackout. The CIA " + + "suspects that Juggernaut is a heavily-augmented Synthoid, and " + + "have thus enlisted our help.

" + + "Your mission is to eradicate Juggernaut and his followers.", + baseDifficulty:10e3, reqdRank:20e3, + rankGain:300, rankLoss:40, hpLoss:300, + weights:{hack:0,str:0.25,def:0.25,dex:0.25,agi:0.25,cha:0, int:0}, + decays:{hack:0,str:0.8,def:0.8,dex:0.8,agi:0.8,cha:0, int:0.75}, + isKill:true, + }); + BlackOperations["Operation Red Dragon"] = new BlackOperation({ + name:"Operation Red Dragon", + desc:"The Tetrads criminal organization is suspected of " + + "reverse-engineering the MK-VI Synthoid design. We believe " + + "they altered and possibly improved the design and began " + + "manufacturing their own Synthoid models in order to bolster " + + "their criminal activities.

" + + "Your task is to infiltrate and destroy the Tetrads' base of operations " + + "in Los Angeles. Intelligence tells us that their base houses " + + "one of their Synthoid manufacturing units.", + baseDifficulty:12.5e3, reqdRank:25e3, + rankGain:500, rankLoss:50, hpLoss:500, + weights:{hack:0.05,str:0.2,def:0.2,dex:0.25,agi:0.25,cha:0, int:0.05}, + decays:{hack:0.6,str:0.8,def:0.8,dex:0.8,agi:0.8,cha:0, int:0.75}, + isKill:true, + }); + BlackOperations["Operation K"] = new BlackOperation({ + name:"Operation K", + desc:"CODE RED SITUATION. Our intelligence tells us that VitaLife " + + "has discovered a new android cloning technology. This technology " + + "is supposedly capable of cloning Synthoid, not only physically " + + "but also their advanced AI modules. We do not believe that " + + "VitaLife is trying to use this technology illegally or " + + "maliciously, but if any Synthoids were able to infiltrate the " + + "corporation and take advantage of this technology then the " + + "results would be catastrophic.

" + + "We do not have the power or jurisdiction to shutdown this down " + + "through legal or political means, so we must resort to a covert " + + "operation. Your goal is to destroy this technology and eliminate " + + "anyone who was involved in its creation.", + baseDifficulty:15e3, reqdRank:30e3, + rankGain:750, rankLoss:60, hpLoss:1000, + weights:{hack:0.05,str:0.2,def:0.2,dex:0.25,agi:0.25,cha:0, int:0.05}, + decays:{hack:0.6,str:0.8,def:0.8,dex:0.8,agi:0.8,cha:0, int:0.75}, + isKill:true + }); + BlackOperations["Operation Deckard"] = new BlackOperation({ + name:"Operation Deckard", + desc:"Despite your success in eliminating VitaLife's new android-replicating " + + "technology in Operation K, we've discovered that a small group of " + + "MK-VI Synthoids were able to make off with the schematics and design " + + "of the technology before the Operation. It is almost a certainty that " + + "these Synthoids are some of the rogue MK-VI ones from the Synthoid Uprising." + + "The goal of Operation Deckard is to hunt down these Synthoids and retire " + + "them. I don't need to tell you how critical this mission is.", + baseDifficulty:20e3, reqdRank:40e3, + rankGain:1e3, rankLoss:75, hpLoss:200, + weights:{hack:0,str:0.24,def:0.24,dex:0.24,agi:0.24,cha:0, int:0.04}, + decays:{hack:0,str:0.8,def:0.8,dex:0.8,agi:0.8,cha:0, int:0.75}, + isKill:true, + }); + BlackOperations["Operation Tyrell"] = new BlackOperation({ + name:"Operation Tyrell", + desc:"A week ago Blade Industries reported a small break-in at one " + + "of their Aevum Augmentation storage facitilities. We figured out " + + "that The Dark Army was behind the heist, and didn't think any more " + + "of it. However, we've just discovered that several known MK-VI Synthoids " + + "were part of that break-in group.

" + + "We cannot have Synthoids upgrading their already-enhanced abilities " + + "with Augmentations. Your task is to hunt down the associated Dark Army " + + "members and eliminate them.", + baseDifficulty:25e3, reqdRank:50e3, + rankGain:1.5e3, rankLoss:100, hpLoss:500, + weights:{hack:0.1,str:0.2,def:0.2,dex:0.2,agi:0.2,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.75}, + isKill:true, + }); + BlackOperations["Operation Wallace"] = new BlackOperation({ + name:"Operation Wallace", + desc:"Based on information gathered from Operation Tyrell, we've discovered " + + "that The Dark Army was well aware that there were Synthoids amongst " + + "their ranks. Even worse, we believe that The Dark Army is working " + + "together with other criminal organizations such as The Syndicate and " + + "that they are planning some sort of large-scale takeover of multiple major " + + "cities, most notably Aevum. We suspect that Synthoids have infiltrated " + + "the ranks of these criminal factions and are trying to stage another " + + "Synthoid uprising.

" + + "The best way to deal with this is to prevent it before it even happens. " + + "The goal of Operation Wallace is to destroy the Dark Army and " + + "Syndicate factions in Aevum immediately. Leave no survivors.", + baseDifficulty:30e3, reqdRank:75e3, + rankGain:2e3, rankLoss:150, hpLoss:1500, + weights:{hack:0,str:0.24,def:0.24,dex:0.24,agi:0.24,cha:0, int:0.04}, + decays:{hack:0.6,str:0.8,def:0.8,dex:0.8,agi:0.8,cha:0, int:0.75}, + isKill:true + }); + BlackOperations["Operation Shoulder of Orion"] = new BlackOperation({ + name:"Operation Shoulder of Orion", + desc:"China's Solaris Space Systems is secretly launching the first " + + "manned spacecraft in over a decade using Synthoids. We believe " + + "China is trying to establish the first off-world colonies.

" + + "The mission is to prevent this launch without instigating an " + + "international conflict. When you accept this mission you will be " + + "officially disavowed by the NSA and the national government until after you " + + "successfully return. In the event of failure, all of the operation's " + + "team members must not let themselves be captured alive.", + baseDifficulty:35e3, reqdRank:100e3, + rankGain:2.5e3, rankLoss:500, hpLoss:1500, + weights:{hack:0.1,str:0.2,def:0.2,dex:0.2,agi:0.2,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.75}, + isStealth:true + }); + BlackOperations["Operation Hyron"] = new BlackOperation({ + name:"Operation Hyron", + desc:"Our intelligence tells us that Fulcrum Technologies is developing " + + "a quantum supercomputer using human brains as core " + + "processors. This supercomputer " + + "is rumored to be able to store vast amounts of data and " + + "perform computations unmatched by any other supercomputer on the " + + "planet. But more importantly, the use of organic human brains " + + "means that the supercomputer may be able to reason abstractly " + + "and become self-aware.

" + + "I do not need to remind you why sentient-level AIs pose a serious " + + "thread to all of mankind.

" + + "The research for this project is being conducted at one of Fulcrum " + + "Technologies secret facilities in Aevum, codenamed 'Alpha Ranch'. " + + "Infiltrate the compound, delete and destroy the work, and then find and kill the " + + "project lead.", + baseDifficulty:40e3, reqdRank:125e3, + rankGain:3e3, rankLoss:1e3, hpLoss:500, + weights:{hack:0.1,str:0.2,def:0.2,dex:0.2,agi:0.2,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.75}, + isKill:true + }); + BlackOperations["Operation Morpheus"] = new BlackOperation({ + name:"Operation Morpheus", + desc:"DreamSense Technologies is an advertising company that uses " + + "special technology to transmit their ads into the peoples " + + "dreams and subconcious. They do this using broadcast transmitter " + + "towers. Based on information from our agents and informants in " + + "Chonqging, we have reason to believe that one of the broadcast " + + "towers there has been compromised by Synthoids and is being used " + + "to spread pro-Synthoid propaganda.

" + + "The mission is to destroy this broadcast tower. Speed and " + + "stealth are of the upmost important for this.", + baseDifficulty:45e3, reqdRank:150e3, + rankGain:4e3, rankLoss:1e3, hpLoss:100, + weights:{hack:0.05,str:0.15,def:0.15,dex:0.3,agi:0.3,cha:0, int:0.05}, + decays:{hack:0.6,str:0.8,def:0.8,dex:0.8,agi:0.8,cha:0, int:0.75}, + isStealth:true + }); + BlackOperations["Operation Ion Storm"] = new BlackOperation({ + name:"Operation Ion Storm", + desc:"Our analysts have uncovered a gathering of MK-VI Synthoids " + + "that have taken up residence in the Sector-12 Slums. We " + + "don't know if they are rogue Synthoids from the Uprising, " + + "but we do know that they have been stockpiling " + + "weapons, money, and other resources. This makes them dangerous.

" + + "This is a full-scale assault operation to find and retire all of these " + + "Synthoids in the Sector-12 Slums.", + baseDifficulty:50e3, reqdRank:175e3, + rankGain:5e3, rankLoss:1e3, hpLoss:5000, + weights:{hack:0,str:0.24,def:0.24,dex:0.24,agi:0.24,cha:0, int:0.04}, + decays:{hack:0.6,str:0.8,def:0.8,dex:0.8,agi:0.8,cha:0, int:0.75}, + isKill:true + }); + BlackOperations["Operation Annihilus"] = new BlackOperation({ + name:"Operation Annihilus", + desc:"Our superiors have ordered us to eradicate everything and everyone " + + "in an underground facility located in Aevum. They tell us " + + "that the facility houses many dangerous Synthoids and " + + "belongs to a terrorist organization called " + + "'The Covenant'. We have no prior intelligence about this " + + "organization, so you are going in blind.", + baseDifficulty:55e3, reqdRank:200e3, + rankGain:7.5e3, rankLoss:1e3, hpLoss:10e3, + weights:{hack:0,str:0.24,def:0.24,dex:0.24,agi:0.24,cha:0, int:0.04}, + decays:{hack:0.6,str:0.8,def:0.8,dex:0.8,agi:0.8,cha:0, int:0.75}, + isKill:true + }); + BlackOperations["Operation Ultron"] = new BlackOperation({ + name:"Operation Ultron", + desc:"OmniTek Incorporated, the original designer and manufacturer of Synthoids, " + + "has notified us of a malfunction in their AI design. This malfunction, " + + "when triggered, causes MK-VI Synthoids to become radicalized and seek out " + + "the destruction of humanity. They say that this bug affects all MK-VI Synthoids, " + + "not just the rogue ones from the Uprising.

" + + "OmniTek has also told us they they believe someone has triggered this " + + "malfunction in a large group of MK-VI Synthoids, and that these newly-radicalized Synthoids " + + "are now amassing in Volhaven to form a terrorist group called Ultron.

" + + "Intelligence suggests Ultron is heavily armed and that their members are " + + "augmented. We believe Ultron is making moves to take control of " + + "and weaponize DeltaOne's Tactical High-Energy Satellite Laser Array (THESLA).

" + + "Your task is to find and destroy Ultron.", + baseDifficulty:60e3, reqdRank:250e3, + rankGain:10e3, rankLoss:2e3, hpLoss:10e3, + weights:{hack:0.1,str:0.2,def:0.2,dex:0.2,agi:0.2,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.75}, + isKill:true + }); + BlackOperations["Operation Centurion"] = new BlackOperation({ + name:"Operation Centurion", + desc:"D)@#)($M)C0293c40($*)@#D0JUMP3Rm0C<*@#)*$)#02c94830c(#$*D)

" + + "Throughout all of humanity's history, we have relied on " + + "technology to survive, conquer, and progress. Its advancement became our primary goal. " + + "And at the peak of human civilization technology turned into " + + "power. Global, absolute power.

" + + "It seems that the universe is not without a sense of irony.

" + + "D)@#)($M)C0293c40($*)@#D0JUMP3Rm0C<*@#)*$)#02c94830c(#$*D)", + baseDifficulty:70e3, reqdRank:300e3, + rankGain:15e3, rankLoss:5e3, hpLoss:10e3, + weights:{hack:0.1,str:0.2,def:0.2,dex:0.2,agi:0.2,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.75}, + }); + BlackOperations["Operation Vindictus"] = new BlackOperation({ + name:"Operation Vindictus", + desc:"D)@#)($M)C0293c40($*)@#D0JUMP3Rm0C<*@#)*$)#02c94830c(#$*D)

" + + "The bits are all around us. The daemons that hold the Node " + + "together can manifest themselves in many different ways.

" + + "D)@#)($M)C0293c40($*)@#D0JUMP3Rm0C<*@#)*$)#02c94830c(#$*D)", + baseDifficulty:75e3, reqdRank:350e3, + rankGain:20e3, rankLoss:20e3, hpLoss:20e3, + weights:{hack:0.1,str:0.2,def:0.2,dex:0.2,agi:0.2,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.75}, + }); + BlackOperations["Operation Daedalus"] = new BlackOperation({ + name:"Operation Daedalus", + desc:"Yesterday we obeyed kings and bent our neck to emperors. " + + "Today we kneel only to truth.", + baseDifficulty:80e3, reqdRank:400e3, + rankGain:40e3, rankLoss:10e3, hpLoss:100e3, + weights:{hack:0.1,str:0.2,def:0.2,dex:0.2,agi:0.2,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.75}, + }); +})() \ No newline at end of file diff --git a/src/Bladeburner/City.ts b/src/Bladeburner/City.ts new file mode 100644 index 000000000..d159eab7e --- /dev/null +++ b/src/Bladeburner/City.ts @@ -0,0 +1,172 @@ + +import { BladeburnerConstants } from "./data/Constants"; +import { getRandomInt } from "../../utils/helpers/getRandomInt"; +import { Generic_fromJSON, Generic_toJSON, Reviver } from "../../utils/JSONReviver"; +import { addOffset } from "../../utils/helpers/addOffset"; + +export class ChangePopulationByCountParams { + estChange: number = 0; + estOffset: number = 0; +} + +export class ChangePopulationByPercentageParams { + nonZero: boolean = false; + changeEstEqually: boolean = false; +} + +export class City { + + /** + * Name of the city. + */ + name: string = ""; + + /** + * Population of the city. + */ + pop: number = 0; + + /** + * Population estimation of the city. + */ + popEst: number = 0; + + /** + * Number of communities in the city. + */ + comms: number = 0; + + /** + * Estimated number of communities in the city. + */ + commsEst: number = 0; + + /** + * Chaos level of the city. + */ + chaos: number = 0; + + constructor(name: string = BladeburnerConstants.CityNames[2]) { + this.name = name; + + // Synthoid population and estimate + this.pop = getRandomInt(BladeburnerConstants.PopulationThreshold, 1.5 * BladeburnerConstants.PopulationThreshold); + this.popEst = this.pop * (Math.random() + 0.5); + + // Number of Synthoid communities population and estimate + this.comms = getRandomInt(5, 150) + this.commsEst = this.comms + getRandomInt(-5, 5); + if (this.commsEst < 0) this.commsEst = 0; + this.chaos = 0; + } + + /** + * p is the percentage, not the multiplier (e.g. pass in p = 5 for 5%) + */ + changeChaosByPercentage(p: number): void { + if (isNaN(p)) {throw new Error("NaN passed into City.chaosChaosByPercentage()");} + if (p === 0) {return;} + this.chaos += this.chaos * (p/100); + if (this.chaos < 0) {this.chaos = 0;} + } + + improvePopulationEstimateByCount(n: number): void { + if (isNaN(n)) {throw new Error("NaN passeed into City.improvePopulationEstimateByCount()");} + if (this.popEst < this.pop) { + this.popEst += n; + if (this.popEst > this.pop) {this.popEst = this.pop;} + } else if (this.popEst > this.pop) { + this.popEst -= n; + if (this.popEst < this.pop) {this.popEst = this.pop;} + } + } + + /** + * p is the percentage, not the multiplier (e.g. pass in p = 5 for 5%) + */ + improvePopulationEstimateByPercentage(p: number, skillMult: number=1): void { + p = p*skillMult; + if (isNaN(p)) {throw new Error("NaN passed into City.improvePopulationEstimateByPercentage()");} + if (this.popEst < this.pop) { + ++this.popEst; // In case estimate is 0 + this.popEst *= (1 + (p/100)); + if (this.popEst > this.pop) {this.popEst = this.pop;} + } else if (this.popEst > this.pop) { + this.popEst *= (1 - (p/100)); + if (this.popEst < this.pop) {this.popEst = this.pop;} + } + } + + improveCommunityEstimate(n: number=1): void { + if (isNaN(n)) {throw new Error("NaN passed into City.improveCommunityEstimate()");} + if (this.commsEst < this.comms) { + this.commsEst += n; + if (this.commsEst > this.comms) {this.commsEst = this.comms;} + } else if (this.commsEst > this.comms) { + this.commsEst -= n; + if (this.commsEst < this.comms) {this.commsEst = this.comms;} + } + } + + /** + * @params options: + * estChange(int): How much the estimate should change by + * estOffset(int): Add offset to estimate (offset by percentage) + */ + changePopulationByCount(n: number, params: ChangePopulationByCountParams=new ChangePopulationByCountParams()): void { + if (isNaN(n)) {throw new Error("NaN passed into City.changePopulationByCount()");} + this.pop += n; + if (params.estChange && !isNaN(params.estChange)) {this.popEst += params.estChange;} + if (params.estOffset) { + this.popEst = addOffset(this.popEst, params.estOffset); + } + this.popEst = Math.max(this.popEst, 0); + } + + /** + * @p is the percentage, not the multiplier. e.g. pass in p = 5 for 5% + * @params options: + * changeEstEqually(bool) - Change the population estimate by an equal amount + * nonZero (bool) - Set to true to ensure that population always changes by at least 1 + */ + changePopulationByPercentage(p: number, params: ChangePopulationByPercentageParams=new ChangePopulationByPercentageParams()): number { + if (isNaN(p)) {throw new Error("NaN passed into City.changePopulationByPercentage()");} + if (p === 0) {return 0;} + let change = Math.round(this.pop * (p/100)); + + // Population always changes by at least 1 + if (params.nonZero && change === 0) { + p > 0 ? change = 1 : change = -1; + } + + this.pop += change; + if (params.changeEstEqually) { + this.popEst += change; + if (this.popEst < 0) {this.popEst = 0;} + } + return change; + } + + changeChaosByCount(n: number): void { + if (isNaN(n)) {throw new Error("NaN passed into City.changeChaosByCount()");} + if (n === 0) {return;} + this.chaos += n; + if (this.chaos < 0) {this.chaos = 0;} + } + + /** + * Initiatizes a City object from a JSON save state. + */ + static fromJSON(value: any): City { + return Generic_fromJSON(City, value.data); + } + + /** + * Serialize the current object to a JSON save state. + */ + toJSON(): any { + return Generic_toJSON("City", this); + } +} + +Reviver.constructors.City = City; diff --git a/src/Bladeburner/Contract.ts b/src/Bladeburner/Contract.ts new file mode 100644 index 000000000..8b9662e66 --- /dev/null +++ b/src/Bladeburner/Contract.ts @@ -0,0 +1,24 @@ +// import { BladeburnerConstants } from "./data/Constants"; +import { Action, IActionParams } from "./Action"; +import { Generic_fromJSON, Generic_toJSON, Reviver } from "../../utils/JSONReviver"; + +export class Contract extends Action { + + constructor(params: IActionParams | null = null) { + super(params); + } + + getActionTypeSkillSuccessBonus(inst: any): number { + return inst.skillMultipliers.successChanceContract; + } + + static fromJSON(value: any): Contract { + return Generic_fromJSON(Contract, value.data); + } + + toJSON(): any { + return Generic_toJSON("Contract", this); + } +} + +Reviver.constructors.Contract = Contract; \ No newline at end of file diff --git a/src/Bladeburner/GeneralActions.ts b/src/Bladeburner/GeneralActions.ts new file mode 100644 index 000000000..209b60002 --- /dev/null +++ b/src/Bladeburner/GeneralActions.ts @@ -0,0 +1,49 @@ +import { Action } from "./Action"; +import { IMap } from "../types"; + +export const GeneralActions: IMap = {}; + +(function() { + // General Actions + let actionName; + actionName = "Training"; + GeneralActions[actionName] = new Action({ + name:actionName, + desc:"Improve your abilities at the Bladeburner unit's specialized training " + + "center. Doing this gives experience for all combat stats and also " + + "increases your max stamina." + }); + + actionName = "Field Analysis"; + GeneralActions[actionName] = new Action({ + name:actionName, + desc:"Mine and analyze Synthoid-related data. This improve the " + + "Bladeburner's unit intelligence on Synthoid locations and " + + "activities. Completing this action will improve the accuracy " + + "of your Synthoid population estimated in the current city.

" + + "Does NOT require stamina." + }); + + actionName = "Recruitment"; + GeneralActions[actionName] = new Action({ + name:actionName, + desc:"Attempt to recruit members for your Bladeburner team. These members " + + "can help you conduct operations.

" + + "Does NOT require stamina." + }); + + actionName = "Diplomacy"; + GeneralActions[actionName] = new Action({ + name: actionName, + desc: "Improve diplomatic relations with the Synthoid population. " + + "Completing this action will reduce the Chaos level in your current city.

" + + "Does NOT require stamina." + }); + + actionName = "Hyperbolic Regeneration Chamber"; + GeneralActions[actionName] = new Action({ + name: actionName, + desc: "Enter cryogenic stasis using the Bladeburner division's hi-tech Regeneration Chamber. " + + "This will slowly heal your wounds and slightly increase your stamina.

", + }); +})() \ No newline at end of file diff --git a/src/Bladeburner/Operation.ts b/src/Bladeburner/Operation.ts new file mode 100644 index 000000000..12718b493 --- /dev/null +++ b/src/Bladeburner/Operation.ts @@ -0,0 +1,55 @@ +import { BladeburnerConstants } from "./data/Constants"; +import { Action, IActionParams } from "./Action"; +import { Generic_fromJSON, Generic_toJSON, Reviver } from "../../utils/JSONReviver"; + +export interface IOperationParams extends IActionParams { + reqdRank?: number; + teamCount?: number; +} + +export class Operation extends Action { + reqdRank: number = 100; + teamCount: number = 0; + + constructor(params: IOperationParams | null = null) { + super(params); + if(params && params.reqdRank) this.reqdRank = params.reqdRank; + if(params && params.teamCount) this.teamCount = params.teamCount; + } + + // For actions that have teams. To be implemented by subtypes. + getTeamSuccessBonus(inst: any): number { + if (this.teamCount && this.teamCount > 0) { + this.teamCount = Math.min(this.teamCount, inst.teamSize); + let teamMultiplier = Math.pow(this.teamCount, 0.05); + return teamMultiplier; + } + + return 1; + } + + getActionTypeSkillSuccessBonus(inst: any): number { + return inst.skillMultipliers.successChanceOperation; + } + + getChaosDifficultyBonus(inst: any, params: any): number { + const city = inst.getCurrentCity(); + if (city.chaos > BladeburnerConstants.ChaosThreshold) { + let diff = 1 + (city.chaos - BladeburnerConstants.ChaosThreshold); + let mult = Math.pow(diff, 0.1); + return mult; + } + + return 1; + } + + static fromJSON(value: any): Operation { + return Generic_fromJSON(Operation, value.data); + } + + toJSON(): any { + return Generic_toJSON("Operation", this); + } +} + +Reviver.constructors.Operation = Operation; \ No newline at end of file diff --git a/src/Bladeburner/Skill.ts b/src/Bladeburner/Skill.ts new file mode 100644 index 000000000..d901a25ca --- /dev/null +++ b/src/Bladeburner/Skill.ts @@ -0,0 +1,107 @@ +import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers"; + +interface ISkillParams { + name: string; + desc: string; + + baseCost?: number; + costInc?: number; + maxLvl?: number; + + successChanceAll?: number; + successChanceStealth?: number; + successChanceKill?: number; + successChanceContract?: number; + successChanceOperation?: number; + successChanceEstimate?: number; + + actionTime?: number; + + effHack?: number; + effStr?: number; + effDef?: number; + effDex?: number; + effAgi?: number; + effCha?: number; + + stamina?: number; + money?: number; + expGain?: number; +} + +export class Skill { + name: string; + desc: string; + // Cost is in Skill Points + baseCost: number = 1; + // Additive cost increase per level + costInc: number = 1; + maxLvl: number = 0; + + /** + * These benefits are additive. So total multiplier will be level (handled externally) times the + * effects below + */ + successChanceAll: number = 0; + successChanceStealth: number = 0; + successChanceKill: number = 0; + successChanceContract: number = 0; + successChanceOperation: number = 0; + + /** + * This multiplier affects everything that increases synthoid population/community estimate + * e.g. Field analysis, Investigation Op, Undercover Op + */ + successChanceEstimate: number = 0; + actionTime: number = 0; + effHack: number = 0; + effStr: number = 0; + effDef: number = 0; + effDex: number = 0; + effAgi: number = 0; + effCha: number = 0; + stamina: number = 0; + money: number = 0; + expGain: number = 0; + + constructor(params: ISkillParams={name:"foo", desc:"foo"}) { + if (!params.name) { + throw new Error("Failed to initialize Bladeburner Skill. No name was specified in ctor"); + } + if (!params.desc) { + throw new Error("Failed to initialize Bladeburner Skills. No desc was specified in ctor"); + } + this.name = params.name; + this.desc = params.desc; + this.baseCost = params.baseCost ? params.baseCost : 1; + this.costInc = params.costInc ? params.costInc : 1; + + if (params.maxLvl) {this.maxLvl = params.maxLvl;} + + if (params.successChanceAll) {this.successChanceAll = params.successChanceAll;} + if (params.successChanceStealth) {this.successChanceStealth = params.successChanceStealth;} + if (params.successChanceKill) {this.successChanceKill = params.successChanceKill;} + if (params.successChanceContract) {this.successChanceContract = params.successChanceContract;} + if (params.successChanceOperation) {this.successChanceOperation = params.successChanceOperation;} + + + if (params.successChanceEstimate) {this.successChanceEstimate = params.successChanceEstimate;} + + if (params.actionTime) {this.actionTime = params.actionTime;} + if (params.effHack) {this.effHack = params.effHack;} + if (params.effStr) {this.effStr = params.effStr;} + if (params.effDef) {this.effDef = params.effDef;} + if (params.effDex) {this.effDex = params.effDex;} + if (params.effAgi) {this.effAgi = params.effAgi;} + if (params.effCha) {this.effCha = params.effCha;} + + if (params.stamina) {this.stamina = params.stamina;} + if (params.money) {this.money = params.money;} + if (params.expGain) {this.expGain = params.expGain;} + } + + calculateCost(currentLevel: number): number { + return Math.floor((this.baseCost + (currentLevel * this.costInc)) * BitNodeMultipliers.BladeburnerSkillCost); + } +} + diff --git a/src/Bladeburner/Skills.ts b/src/Bladeburner/Skills.ts new file mode 100644 index 000000000..f762a296e --- /dev/null +++ b/src/Bladeburner/Skills.ts @@ -0,0 +1,90 @@ +import { Skill } from "./Skill"; +import { SkillNames } from "./data/SkillNames"; +import { IMap } from "../types"; + +export const Skills: IMap = {}; + +(function(){ + Skills[SkillNames.BladesIntuition] = new Skill({ + name:SkillNames.BladesIntuition, + desc:"Each level of this skill increases your success chance " + + "for all Contracts, Operations, and BlackOps by 3%", + baseCost: 3, costInc: 2.1, + successChanceAll:3 + }); + Skills[SkillNames.Cloak] = new Skill({ + name:SkillNames.Cloak, + desc:"Each level of this skill increases your " + + "success chance in stealth-related Contracts, Operations, and BlackOps by 5.5%", + baseCost: 2, costInc: 1.1, + successChanceStealth:5.5 + }); + Skills[SkillNames.ShortCircuit] = new Skill({ + name:SkillNames.ShortCircuit, + desc:"Each level of this skill increases your success chance " + + "in Contracts, Operations, and BlackOps that involve retirement by 5.5%", + baseCost: 2, costInc: 2.1, + successChanceKill:5.5 + }); + Skills[SkillNames.DigitalObserver] = new Skill({ + name:SkillNames.DigitalObserver, + desc:"Each level of this skill increases your success chance in " + + "all Operations and BlackOps by 4%", + baseCost: 2, costInc: 2.1, + successChanceOperation:4 + }); + Skills[SkillNames.Tracer] = new Skill({ + name:SkillNames.Tracer, + desc:"Each level of this skill increases your success chance in " + + "all Contracts by 4%", + baseCost: 2, costInc: 2.1, + successChanceContract:4 + }); + Skills[SkillNames.Overclock] = new Skill({ + name:SkillNames.Overclock, + desc:"Each level of this skill decreases the time it takes " + + "to attempt a Contract, Operation, and BlackOp by 1% (Max Level: 90)", + baseCost: 3, costInc: 1.4, maxLvl: 90, + actionTime:1 + }); + Skills[SkillNames.Reaper] = new Skill({ + name: SkillNames.Reaper, + desc: "Each level of this skill increases your effective combat stats for Bladeburner actions by 2%", + baseCost: 2, costInc: 2.1, + effStr: 2, effDef: 2, effDex: 2, effAgi: 2 + }); + Skills[SkillNames.EvasiveSystem] = new Skill({ + name:SkillNames.EvasiveSystem, + desc:"Each level of this skill increases your effective " + + "dexterity and agility for Bladeburner actions by 4%", + baseCost: 2, costInc: 2.1, + effDex: 4, effAgi: 4 + }); + Skills[SkillNames.Datamancer] = new Skill({ + name:SkillNames.Datamancer, + desc:"Each level of this skill increases your effectiveness in " + + "synthoid population analysis and investigation by 5%. " + + "This affects all actions that can potentially increase " + + "the accuracy of your synthoid population/community estimates.", + baseCost:3, costInc:1, + successChanceEstimate:5 + }); + Skills[SkillNames.CybersEdge] = new Skill({ + name:SkillNames.CybersEdge, + desc:"Each level of this skill increases your max stamina by 2%", + baseCost:1, costInc:3, + stamina:2 + }); + Skills[SkillNames.HandsOfMidas] = new Skill({ + name: SkillNames.HandsOfMidas, + desc: "Each level of this skill increases the amount of money you receive from Contracts by 10%", + baseCost: 2, costInc: 2.5, + money: 10, + }); + Skills[SkillNames.Hyperdrive] = new Skill({ + name: SkillNames.Hyperdrive, + desc: "Each level of this skill increases the experience earned from Contracts, Operations, and BlackOps by 10%", + baseCost: 1, costInc: 2.5, + expGain: 10, + }); +})() \ No newline at end of file diff --git a/src/Bladeburner/data/ActionTypes.ts b/src/Bladeburner/data/ActionTypes.ts new file mode 100644 index 000000000..f4392313c --- /dev/null +++ b/src/Bladeburner/data/ActionTypes.ts @@ -0,0 +1,14 @@ +// Action Identifier enum +export const ActionTypes = Object.freeze({ + "Idle": 1, + "Contract": 2, + "Operation": 3, + "BlackOp": 4, + "BlackOperation": 4, + "Training": 5, + "Recruitment": 6, + "FieldAnalysis": 7, + "Field Analysis": 7, + "Diplomacy": 8, + "Hyperbolic Regeneration Chamber": 9, +}); \ No newline at end of file diff --git a/src/Bladeburner/data/Constants.ts b/src/Bladeburner/data/Constants.ts new file mode 100644 index 000000000..62a0ddb3a --- /dev/null +++ b/src/Bladeburner/data/Constants.ts @@ -0,0 +1,79 @@ +export const BladeburnerConstants: { + CityNames: string[]; + CyclesPerSecond: number; + StaminaGainPerSecond: number; + BaseStaminaLoss: number; + MaxStaminaToGainFactor: number; + DifficultyToTimeFactor: number; + DiffMultExponentialFactor: number; + DiffMultLinearFactor: number; + EffAgiLinearFactor: number; + EffDexLinearFactor: number; + EffAgiExponentialFactor: number; + EffDexExponentialFactor: number; + BaseRecruitmentTimeNeeded: number; + PopulationThreshold: number; + PopulationExponent: number; + ChaosThreshold: number; + BaseStatGain: number; + BaseIntGain: number; + ActionCountGrowthPeriod: number; + RankToFactionRepFactor: number; + RankNeededForFaction: number; + ContractSuccessesPerLevel: number; + OperationSuccessesPerLevel: number; + RanksPerSkillPoint: number; + ContractBaseMoneyGain: number; + HrcHpGain: number; + HrcStaminaGain: number; +} = { + CityNames: ["Aevum", "Chongqing", "Sector-12", "New Tokyo", "Ishima", "Volhaven"], + CyclesPerSecond: 5, // Game cycle is 200 ms + + StaminaGainPerSecond: 0.0085, + BaseStaminaLoss: 0.285, // Base stamina loss per action. Increased based on difficulty + MaxStaminaToGainFactor: 70000, // Max Stamina is divided by this to get bonus stamina gain + + DifficultyToTimeFactor: 10, // Action Difficulty divided by this to get base action time + + /** + * The difficulty multiplier affects stamina loss and hp loss of an action. Also affects + * experience gain. Its formula is: + * difficulty ^ exponentialFactor + difficulty / linearFactor + */ + DiffMultExponentialFactor: 0.28, + DiffMultLinearFactor: 650, + + /** + * These factors are used to calculate action time. + * They affect how much action time is reduced based on your agility and dexterity + */ + EffAgiLinearFactor: 10e3, + EffDexLinearFactor: 10e3, + EffAgiExponentialFactor: 0.04, + EffDexExponentialFactor: 0.035, + + BaseRecruitmentTimeNeeded: 300, // Base time needed (s) to complete a Recruitment action + + PopulationThreshold: 1e9, // Population which determines baseline success rate + PopulationExponent: 0.7, // Exponent that influences how different populations affect success rate + ChaosThreshold: 50, // City chaos level after which it starts making tasks harder + + BaseStatGain: 1, // Base stat gain per second + BaseIntGain: 0.001, // Base intelligence stat gain + + ActionCountGrowthPeriod: 480, // Time (s) it takes for action count to grow by its specified value + + RankToFactionRepFactor: 2, // Delta Faction Rep = this * Delta Rank + RankNeededForFaction: 25, + + ContractSuccessesPerLevel: 3, // How many successes you need to level up a contract + OperationSuccessesPerLevel: 2.5, // How many successes you need to level up an op + + RanksPerSkillPoint: 3, // How many ranks needed to get 1 Skill Point + + ContractBaseMoneyGain: 250e3, // Base Money Gained per contract + + HrcHpGain: 2, // HP Gained from Hyperbolic Regeneration chamber + HrcStaminaGain: 1, // Percentage Stamina gained from Hyperbolic Regeneration Chamber +} \ No newline at end of file diff --git a/src/Bladeburner/data/Help.ts b/src/Bladeburner/data/Help.ts new file mode 100644 index 000000000..ed87118b8 --- /dev/null +++ b/src/Bladeburner/data/Help.ts @@ -0,0 +1,116 @@ +export const ConsoleHelpText: {} = { + helpList: [ + "Use 'help [command]' to get more information about a particular Bladeburner console command.", + "", + " automate [var] [val] [hi/low] Configure simple automation for Bladeburner tasks", + " clear/cls Clear the console", + " help [cmd] Display this help text, or help text for a specific command", + " log [en/dis] [type] Enable or disable logging for events and actions", + " skill [action] [name] Level or display info about your Bladeburner skills", + " start [type] [name] Start a Bladeburner action/task" , + " stop Stops your current Bladeburner action/task" + ], + automate: [ + "automate [var] [val] [hi/low]", + "", + "A simple way to automate your Bladeburner actions. This console command can be used " + + "to automatically start an action when your stamina rises above a certain threshold, and " + + "automatically switch to another action when your stamina drops below another threshold.", + " automate status - Check the current status of your automation and get a brief description of what it'll do", + " automate en - Enable the automation feature", + " automate dis - Disable the automation feature", + "", + "There are four properties that must be set for this automation to work properly. Here is how to set them:", + "", + " automate stamina 100 high", + " automate contract Tracking high", + " automate stamina 50 low", + " automate general 'Field Analysis' low", + "", + "Using the four console commands above will set the automation to perform Tracking contracts " + + "if your stamina is 100 or higher, and then switch to Field Analysis if your stamina drops below " + + "50. Note that when setting the action, the name of the action is CASE-SENSITIVE. It must " + + "exactly match whatever the name is in the UI." + ], + clear: [ + "clear", + "", + "Clears the console" + ], + cls: [ + "cls", + "", + "Clears the console" + ], + help: [ + "help [command]", + "", + "Running 'help' with no arguments displays the general help text, which lists all console commands " + + "and a brief description of what they do. A command can be specified to get more specific help text " + + "about that particular command. For example:", + "", + " help automate", + "", + "will display specific information about using the automate console command" + ], + log: [ + "log [en/dis] [type]", + "", + "Enable or disable logging. By default, the results of completing actions such as contracts/operations are logged " + + "in the console. There are also random events that are logged in the console as well. The five categories of " + + "things that get logged are:", + "", + "[general, contracts, ops, blackops, events]", + "", + "The logging for these categories can be enabled or disabled like so:", + "", + " log dis contracts - Disables logging that occurs when contracts are completed", + " log en contracts - Enables logging that occurs when contracts are completed", + " log dis events - Disables logging for Bladeburner random events", + "", + "Logging can be universally enabled/disabled using the 'all' keyword:", + "", + " log dis all", + " log en all" + ], + skill: [ + "skill [action] [name]", + "", + "Level or display information about your skills.", + "", + "To display information about all of your skills and your multipliers, use:", + "", + " skill list", + "", + "To display information about a specific skill, specify the name of the skill afterwards. " + + "Note that the name of the skill is case-sensitive. Enter it exactly as seen in the UI. If " + + "the name of the skill has whitespace, enclose the name of the skill in double quotation marks:", + "", + " skill list Reaper
" + + " skill list 'Digital Observer'", + "", + "This console command can also be used to level up skills:", + "", + " skill level [skill name]" + ], + start: [ + "start [type] [name]", + "", + "Start an action. An action is specified by its type and its name. The " + + "name is case-sensitive. It must appear exactly as it does in the UI. If " + + "the name of the action has whitespace, enclose it in double quotation marks. " + + "Valid action types include:", + "", + "[general, contract, op, blackop]", + "", + "Examples:", + "", + " start contract Tracking", + " start op 'Undercover Operation'" + ], + stop:[ + "stop", + "", + "Stop your current action and go idle." + ], +} \ No newline at end of file diff --git a/src/Bladeburner/data/SkillNames.ts b/src/Bladeburner/data/SkillNames.ts new file mode 100644 index 000000000..1a71bd63c --- /dev/null +++ b/src/Bladeburner/data/SkillNames.ts @@ -0,0 +1,31 @@ +export const SkillNames: { + BladesIntuition: string; + Cloak: string; + Marksman: string; + WeaponProficiency: string; + ShortCircuit: string; + DigitalObserver: string; + Tracer: string; + Overclock: string; + Reaper: string; + EvasiveSystem: string; + Datamancer: string; + CybersEdge: string; + HandsOfMidas: string; + Hyperdrive: string; +} = { + BladesIntuition: "Blade's Intuition", + Cloak: "Cloak", + Marksman: "Marksman", + WeaponProficiency: "Weapon Proficiency", + ShortCircuit: "Short-Circuit", + DigitalObserver: "Digital Observer", + Tracer: "Tracer", + Overclock: "Overclock", + Reaper: "Reaper", + EvasiveSystem: "Evasive System", + Datamancer: "Datamancer", + CybersEdge: "Cyber's Edge", + HandsOfMidas: "Hands of Midas", + Hyperdrive: "Hyperdrive", +} \ No newline at end of file diff --git a/src/Bladeburner/ui/BlackOperationsPage.tsx b/src/Bladeburner/ui/BlackOperationsPage.tsx new file mode 100644 index 000000000..134147551 --- /dev/null +++ b/src/Bladeburner/ui/BlackOperationsPage.tsx @@ -0,0 +1,63 @@ +import { BlackOperations } from "../BlackOperations"; +/* +if (DomElems.actionsAndSkillsList == null || DomElems.actionsAndSkillsDesc == null) { + throw new Error("Bladeburner.createBlackOpsContent called with either " + + "DomElems.actionsAndSkillsList or DomElems.actionsAndSkillsDesc = null"); +} + +DomElems.actionsAndSkillsDesc.innerHTML = + "Black Operations (Black Ops) are special, one-time covert operations. " + + "Each Black Op must be unlocked successively by completing " + + "the one before it.

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

" + + "Like normal operations, you may use a team for Black Ops. Failing " + + "a black op will incur heavy HP and rank losses."; + +// Put Black Operations in sequence of required rank +var blackops = []; +for (var blackopName in BlackOperations) { + if (BlackOperations.hasOwnProperty(blackopName)) { + blackops.push(BlackOperations[blackopName]); + } +} +blackops.sort(function(a, b) { + return (a.reqdRank - b.reqdRank); +}); + +for (var i = blackops.length-1; i >= 0 ; --i) { + if (this.blackops[[blackops[i].name]] == null && i !== 0 && this.blackops[[blackops[i-1].name]] == null) {continue;} // If this one nor the next are completed then this isn't unlocked yet. + DomElems.blackops[blackops[i].name] = createElement("div", { + class:"bladeburner-action", name:blackops[i].name + }); + DomElems.actionsAndSkillsList.appendChild(DomElems.blackops[blackops[i].name]); +} +*/ + + + +import * as React from "react"; + +export function BlackOperationsPage(inst: any): React.ReactElement { + // Put Black Operations in sequence of required rank + const blackops = []; + for (const name in BlackOperations) { + if (BlackOperations.hasOwnProperty(name)) { + blackops.push(BlackOperations[name]); + } + } + blackops.sort(function(a, b) { + return (a.reqdRank - b.reqdRank); + }); + + return (
+

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

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

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

+ {blackops.map( op => +
+
+ )} +
) +} diff --git a/src/Constants.ts b/src/Constants.ts index 9cddaa04b..a5320d689 100644 --- a/src/Constants.ts +++ b/src/Constants.ts @@ -242,6 +242,12 @@ export let CONSTANTS: IMap = { Gang * style improvements + Bladeburner + * style improvements + + Bladeburner + * fix bug where 'skill list SKILL' would crash if skill is level 0. + Sleeve * karma gain now scales with sync. ` diff --git a/src/NetscriptFunctions.js b/src/NetscriptFunctions.js index c833a6bfa..eb1633e52 100644 --- a/src/NetscriptFunctions.js +++ b/src/NetscriptFunctions.js @@ -431,14 +431,18 @@ function NetscriptFunctions(workerScript) { } } - const checkBladeburnerAccess = function(func) { - const accessDenied = `You do not ` + - "currently have access to the Bladeburner API. To access the Bladeburner API " + - "you must be employed at the Bladeburner division, AND you must either be in " + - "BitNode-7 or have Source-File 7."; - const hasAccess = Player.bladeburner instanceof Bladeburner && (Player.bitNodeN === 7 || Player.sourceFiles.some(a=>{return a.n === 7})); - if(!hasAccess) { - throw makeRuntimeErrorMsg(`bladeburner.${func}`, accessDenied); + const checkBladeburnerAccess = function(func, skipjoined=false) { + const apiAccess = (Player.bitNodeN === 7 || Player.sourceFiles.some(a=>{return a.n === 7})); + if (!apiAccess) { + const apiDenied = `You do not currently have access to the Bladeburner API. You must either be in BitNode-7 or have Source-File 7.`; + throw makeRuntimeErrorMsg(`bladeburner.${func}`, apiDenied); + } + if (!skipjoined) { + const bladeburnerAccess = Player.bladeburner instanceof Bladeburner; + if(!bladeburnerAccess) { + const bladeburnerDenied = `You must be a member of the Bladeburner division to use this API.`; + throw makeRuntimeErrorMsg(`bladeburner.${func}`, bladeburnerDenied); + } } } @@ -707,17 +711,19 @@ function NetscriptFunctions(workerScript) { maxThreadNeeded = 1e6; } - let moneyGained = Math.floor(server.moneyAvailable * percentHacked) * threads; + let moneyDrained = Math.floor(server.moneyAvailable * percentHacked) * threads; // Over-the-top safety checks - if (moneyGained <= 0) { - moneyGained = 0; + if (moneyDrained <= 0) { + moneyDrained = 0; expGainedOnSuccess = expGainedOnFailure; } - if (moneyGained > server.moneyAvailable) {moneyGained = server.moneyAvailable;} - server.moneyAvailable -= moneyGained; + if (moneyDrained > server.moneyAvailable) {moneyDrained = server.moneyAvailable;} + server.moneyAvailable -= moneyDrained; if (server.moneyAvailable < 0) {server.moneyAvailable = 0;} + const moneyGained = moneyDrained * BitNodeMultipliers.ScriptHackMoneyGain; + Player.gainMoney(moneyGained); workerScript.scriptRef.onlineMoneyMade += moneyGained; Player.scriptProdSinceLastAug += moneyGained; @@ -1681,7 +1687,8 @@ function NetscriptFunctions(workerScript) { checkTixApiAccess("buyStock"); const stock = getStockFromSymbol(symbol, "buyStock"); const res = buyStock(stock, shares, workerScript, { rerenderFn: displayStockMarketContent }); - + console.log(stock); + console.log(res); return res ? stock.price : 0; }, sellStock: function(symbol, shares) { @@ -3696,12 +3703,12 @@ function NetscriptFunctions(workerScript) { }, joinBladeburnerFaction: function() { updateDynamicRam("joinBladeburnerFaction", getRamCost("bladeburner", "joinBladeburnerFaction")); - checkBladeburnerAccess("joinBladeburnerFaction"); + checkBladeburnerAccess("joinBladeburnerFaction", true); return Player.bladeburner.joinBladeburnerFactionNetscriptFn(workerScript); }, joinBladeburnerDivision: function() { updateDynamicRam("joinBladeburnerDivision", getRamCost("bladeburner", "joinBladeburnerDivision")); - checkBladeburnerAccess("joinBladeburnerDivision"); + checkBladeburnerAccess("joinBladeburnerDivision", true); if ((Player.bitNodeN === 7 || SourceFileFlags[7] > 0)) { if (Player.bitNodeN === 8) { return false; } if (Player.bladeburner instanceof Bladeburner) { diff --git a/src/PersonObjects/IPlayer.ts b/src/PersonObjects/IPlayer.ts index 361ed2212..046b27861 100644 --- a/src/PersonObjects/IPlayer.ts +++ b/src/PersonObjects/IPlayer.ts @@ -176,4 +176,5 @@ export interface IPlayer { startWorkPartTime(companyName: string): void; travel(to: CityName): boolean; giveExploit(exploit: Exploit): void; + queryStatFromString(str: string): number; }