mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2024-11-24 00:23:49 +01:00
3607 lines
160 KiB
JavaScript
3607 lines
160 KiB
JavaScript
import {CONSTANTS} from "./Constants.js";
|
|
import {Engine} from "./engine.js";
|
|
import {Faction, Factions, factionExists,
|
|
joinFaction, displayFactionContent} from "./Faction.js";
|
|
import {Locations} from "./Location.js";
|
|
import {Player} from "./Player.js";
|
|
import {hackWorldDaemon} from "./RedPill.js";
|
|
import {KEY} from "./Terminal.js";
|
|
|
|
import {dialogBoxCreate} from "../utils/DialogBox.js";
|
|
import {getRandomInt, addOffset, clearObject,
|
|
createElement, removeChildrenFromElement,
|
|
exceptionAlert, createPopup, appendLineBreaks,
|
|
removeElementById, removeElement,
|
|
createProgressBarText} from "../utils/HelperFunctions.js";
|
|
import {Reviver, Generic_toJSON,
|
|
Generic_fromJSON} from "../utils/JSONReviver.js";
|
|
import numeral from "numeral/min/numeral.min";
|
|
import {formatNumber} from "../utils/StringHelperFunctions.js";
|
|
|
|
|
|
var CityNames = ["Aevum", "Chongqing", "Sector-12", "New Tokyo", "Ishima", "Volhaven"];
|
|
|
|
var CyclesPerSecond = 5; //Game cycle is 200 ms
|
|
|
|
var StaminaGainPerSecond = 0.0085;
|
|
var BaseStaminaLoss = 0.285; //Base stamina loss per action. Increased based on difficulty
|
|
var MaxStaminaToGainFactor = 70000; //Max Stamina is divided by this to get bonus stamina gain
|
|
|
|
var DifficultyToTimeFactor = 10; //Action Difficulty divided by this to get base action time
|
|
|
|
//The difficulty multiplier affects stamina loss and hp loss of an action. Its formula is:
|
|
//difficulty ^ exponentialFactor + difficulty / linearFactor
|
|
var DiffMultExponentialFactor = 0.28;
|
|
var DiffMultLinearFactor = 670;
|
|
|
|
var BaseRecruitmentTimeNeeded = 300; //Base time needed (s) to complete a Recruitment action
|
|
|
|
var PopulationThreshold = 1e9; //Population at which success rates start being affected
|
|
var ChaosThreshold = 50; //City chaos level after which it starts making tasks harder
|
|
|
|
var BaseStatGain = 1; //Base stat gain per second
|
|
var BaseIntGain = 0.001; //Base intelligence stat gain
|
|
|
|
var ActionCountGrowthPeriod = 300; //Time (s) it takes for action count to grow by its specified value
|
|
|
|
var RankToFactionRepFactor = 2; //Delta Faction Rep = this * Delta Rank
|
|
|
|
var ContractSuccessesPerLevel = 15; //How many successes you need to level up a contract
|
|
var OperationSuccessesPerLevel = 10; //How many successes you need to level up an op
|
|
|
|
var RanksPerSkillPoint = 4; //How many ranks needed to get 1 Skill Point
|
|
|
|
var ContractBaseMoneyGain = 5e3; //Base Money Gained per contract
|
|
|
|
//DOM related variables
|
|
var ActiveActionCssClass = "bladeburner-active-action";
|
|
|
|
//Console related stuff
|
|
var consoleHistory = []; //Console command history
|
|
var consoleHistoryIndex = 0;
|
|
var consoleHelpText = {
|
|
helpList:"Use 'help [command]' to get more information about a particular Bladeburner console command.<br><br>" +
|
|
"automate [var] [val] [hi/low] Configure simple automation for Bladeburner tasks<br>" +
|
|
"clear/cls Clear the console<br>" +
|
|
"help [cmd] Display this help text, or help text for a specific command<br>" +
|
|
"log [en/dis] [type] Enable or disable logging for events and actions<br>" +
|
|
"skill [action] [name] Level or display info about your Bladeburner skills<br>" +
|
|
"start [type] [name] Start a Bladeburner action/task<br>" +
|
|
"stop Stops your current Bladeburner action/task<br>",
|
|
automate:"automate [var] [val] [hi/low]<br><br>" +
|
|
"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.<br><br>" +
|
|
"automate status - Check the current status of your automation and get a brief description of what it'll do<br>" +
|
|
"automate en - Enable the automation feature<br>" +
|
|
"automate dis - Disable the automation feature<br><br>" +
|
|
"There are four properties that must be set for this automation to work properly. Here is how to set them:<br><br>" +
|
|
"automate stamina 100 high<br>" +
|
|
"automate contract Tracking high<br>" +
|
|
"automate stamina 50 low<br>" +
|
|
'automate general "Field Analysis" low<br><br>' +
|
|
"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<br><br>Clears the console",
|
|
cls:"cls<br><br>Clears the console",
|
|
help:"help [command]<br><br>" +
|
|
"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:<br><br>" +
|
|
"help automate<br><br>" +
|
|
"will display specific information about using the automate console command",
|
|
log:"log [en/dis] [type]<br><br>" +
|
|
"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:<br><br>" +
|
|
"[general, contracts, ops, blackops, events]<br><br>" +
|
|
"The logging for these categories can be enabled or disabled like so:<br><br>" +
|
|
"log dis contracts - Disables logging that occurs when contracts are completed<br>" +
|
|
"log en contracts - Enables logging that occurs when contracts are completed<br>" +
|
|
"log dis events - Disables logging for Bladeburner random events<br><br>" +
|
|
"Logging can be universally enabled/disabled using the 'all' keyword:<br><br>" +
|
|
"log dis all<br>" +
|
|
"log en all",
|
|
skill:"skill [action] [name]<br><br>" +
|
|
"Level or display information about your skills.<br><br>" +
|
|
"To display information about all of your skills and your multipliers, use:<br><br>" +
|
|
"skill list<br><br>" +
|
|
"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:<br><br>" +
|
|
"skill list Reaper<br>" +
|
|
'skill list "Digital Observer"<br><br>' +
|
|
"This console command can also be used to level up skills:<br><br>" +
|
|
"skill level [skill name]",
|
|
start:"start [type] [name]<br><br>" +
|
|
"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:<br><br>" +
|
|
"[general, contract, op, blackop]<br><br>" +
|
|
"Examples:<br><br>" +
|
|
'start contract Tracking<br>' +
|
|
'start op "Undercover Operation"<br>',
|
|
stop:"stop<br><br>" +
|
|
"Stop your current action and go idle",
|
|
}
|
|
|
|
//Keypresses for Console
|
|
$(document).keydown(function(event) {
|
|
if (Engine.currentPage === Engine.Page.Bladeburner) {
|
|
//if (DomElems.consoleInput && !event.ctrlKey && !event.shiftKey && !event.altKey) {
|
|
// DomElems.consoleInput.focus();
|
|
//}
|
|
|
|
if (!(Player.bladeburner instanceof Bladeburner)) {return;}
|
|
|
|
//NOTE: Keycodes imported from Terminal.js
|
|
if (event.keyCode === KEY.ENTER) {
|
|
event.preventDefault();
|
|
var command = DomElems.consoleInput.value;
|
|
if (command.length > 0) {
|
|
Player.bladeburner.postToConsole("> " + command);
|
|
Player.bladeburner.resetConsoleInput();
|
|
Player.bladeburner.executeConsoleCommands(command);
|
|
}
|
|
}
|
|
|
|
if (event.keyCode === KEY.UPARROW) {
|
|
if (DomElems.consoleInput == null) {return;}
|
|
var i = consoleHistoryIndex;
|
|
var len = consoleHistory.length;
|
|
|
|
if (len === 0) {return;}
|
|
if (i < 0 || i > len) {
|
|
consoleHistoryIndex = len;
|
|
}
|
|
|
|
if (i !== 0) {
|
|
--consoleHistoryIndex;
|
|
}
|
|
|
|
var prevCommand = consoleHistory[consoleHistoryIndex];
|
|
DomElems.consoleInput.value = prevCommand;
|
|
setTimeout(function(){DomElems.consoleInput.selectionStart = DomElems.consoleInput.selectionEnd = 10000; }, 0);
|
|
}
|
|
|
|
if (event.keyCode === KEY.DOWNARROW) {
|
|
if (DomElems.consoleInput == null) {return;}
|
|
var i = consoleHistoryIndex;
|
|
var len = consoleHistory.length;
|
|
|
|
if (len == 0) {return;}
|
|
if (i < 0 || i > len) {
|
|
consoleHistoryIndex = len;
|
|
}
|
|
|
|
//Latest command, put nothing
|
|
if (i == len || i == len-1) {
|
|
consoleHistoryIndex = len;
|
|
DomElems.consoleInput.value = "";
|
|
} else {
|
|
++consoleHistoryIndex;
|
|
var prevCommand = consoleHistory[consoleHistoryIndex];
|
|
DomElems.consoleInput.value = prevCommand;
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
function City(params={}) {
|
|
this.name = params.name ? params.name : Locations.Sector12;
|
|
|
|
//Synthoid population and estimate
|
|
this.pop = params.pop ? params.pop : getRandomInt(800e6, 1.2*PopulationThreshold);
|
|
this.popEst = this.pop * (Math.random() + 0.5);
|
|
|
|
//Number of Synthoid communities population and estimate
|
|
this.comms = params.comms ? params.comms : getRandomInt(1, 40);
|
|
this.commsEst = this.comms + getRandomInt(-2, 2);
|
|
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;}
|
|
|
|
//Equipment
|
|
if (params.weaponAbility) {this.weaponAbility = params.weaponAbility;}
|
|
if (params.gunAbility) {this.gunAbility = params.gunAbility;}
|
|
}
|
|
var Skills = {};
|
|
var SkillNames = {
|
|
BladesIntuition: "Blade's Intuition",
|
|
Reaper: "Reaper",
|
|
Cloak: "Cloak",
|
|
Marksman: "Marksman",
|
|
WeaponProficiency: "Weapon Proficiency",
|
|
Overclock: "Overclock",
|
|
EvasiveSystem: "Evasive System",
|
|
ShortCircuit: "Short-Circuit",
|
|
DigitalObserver: "Digital Observer",
|
|
Datamancer: "Datamancer",
|
|
Tracer: "Tracer",
|
|
CybersEdge: "Cyber's Edge"
|
|
}
|
|
|
|
//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
|
|
//Exact formula is not set in stone
|
|
//Initial design: baseDifficulty * (difficultyFac ^ level)?
|
|
//difficulty Fac is slightly greater than 1
|
|
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
|
|
var sum = 0;
|
|
for (var 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)
|
|
var 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 (var 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.log("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 *= (city.popEst / PopulationThreshold);
|
|
} else {
|
|
competence *= (city.pop / PopulationThreshold);
|
|
}
|
|
|
|
//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
|
|
// @inst - Bladeburner Object
|
|
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, 0.03) + Math.pow(effDexterity, 0.03)); //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.toJSON = function() {
|
|
return Generic_toJSON("Action", this);
|
|
}
|
|
Action.fromJSON = function(value) {
|
|
return Generic_fromJSON(Action, value.data);
|
|
}
|
|
Reviver.constructors.Action = Action;
|
|
var GeneralActions = {}; //Training, Field Analysis, Recruitment, etc.
|
|
|
|
//Action Identifier
|
|
var ActionTypes = Object.freeze({
|
|
"Idle": 1,
|
|
"Contract": 2,
|
|
"Operation": 3,
|
|
"BlackOp": 4,
|
|
"BlackOperation": 4,
|
|
"Training": 5,
|
|
"Recruitment": 6,
|
|
"FieldAnalysis": 7,
|
|
"Field Analysis": 7
|
|
});
|
|
function ActionIdentifier(params={}) {
|
|
if (params.name) {this.name = params.name;}
|
|
if (params.type) {this.type = params.type;}
|
|
}
|
|
ActionIdentifier.prototype.toJSON = function() {
|
|
return Generic_toJSON("ActionIdentifier", this);
|
|
}
|
|
ActionIdentifier.fromJSON = function(value) {
|
|
return Generic_fromJSON(ActionIdentifier, value.data);
|
|
}
|
|
Reviver.constructors.ActionIdentifier = ActionIdentifier;
|
|
|
|
//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;
|
|
var BlackOperations = {};
|
|
|
|
function Bladeburner(params={}) {
|
|
this.numHosp = 0; //Number of hospitalizations
|
|
this.moneyLost = 0; //Money lost due to hospitalizations
|
|
this.rank = 0;
|
|
this.maxRank = 0; //Used to determine skill points
|
|
|
|
this.skillPoints = 0;
|
|
this.totalSkillPoints = 0;
|
|
|
|
this.teamSize = 0; //Number of team members
|
|
this.teamLost = 0; //Number of team members lost
|
|
|
|
this.storedCycles = 0;
|
|
|
|
this.randomEventCounter = getRandomInt(300, 600); //5-10 minutes
|
|
|
|
//These times are in seconds
|
|
this.actionTimeToComplete = 0; //0 or -1 is an infinite running action (like training)
|
|
this.actionTimeCurrent = 0;
|
|
|
|
//ActionIdentifier Object
|
|
var idleActionType = ActionTypes["Idle"];
|
|
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]});
|
|
}
|
|
this.city = Locations.Sector12;
|
|
|
|
//Map of SkillNames -> level
|
|
this.skills = {};
|
|
this.skillMultipliers = {};
|
|
this.updateSkillMultipliers(); //Calls resetSkillMultipliers()
|
|
|
|
//Max Stamina is based on stats and Bladeburner-specific bonuses
|
|
this.staminaBonus = 0; //Gained from training
|
|
this.maxStamina = 0;
|
|
this.calculateMaxStamina();
|
|
this.stamina = this.maxStamina;
|
|
|
|
//Contracts and Operations objects. These objects have unique
|
|
//properties because they are randomized in each instance and have stats like
|
|
//successes/failures, so they need to be saved/loaded by the game.
|
|
this.contracts = {};
|
|
this.operations = {};
|
|
|
|
//Object that contains name of all Black Operations that have been completed
|
|
this.blackops = {};
|
|
|
|
//Flags for whether these actions should be logged to console
|
|
this.logging = {
|
|
general:true,
|
|
contracts:true,
|
|
ops:true,
|
|
blackops:true,
|
|
events:true,
|
|
}
|
|
|
|
//Simple automation values
|
|
this.automateEnabled = false;
|
|
this.automateActionHigh = 0;
|
|
this.automateThreshHigh = 0; //Stamina Threshold
|
|
this.automateActionLow = 0;
|
|
this.automateThreshLow = 0; //Stamina Threshold
|
|
|
|
//Initialization
|
|
initBladeburner();
|
|
this.initializeDomElementRefs();
|
|
if (params.new) {this.create();}
|
|
}
|
|
|
|
Bladeburner.prototype.create = function() {
|
|
this.contracts["Tracking"] = new Contract({
|
|
name:"Tracking",
|
|
desc:"Identify and locate Synthoids. This contract involves reconnaissance " +
|
|
"and information-gathering ONLY. Do NOT engage. Stealth is of the utmost importance.<br><br>" +
|
|
"Successfully completing Tracking contracts will slightly improve your Synthoid population estimate for " +
|
|
"whatever city you are currently in.",
|
|
baseDifficulty:125,difficultyFac:1.02,rewardFac:1.041,
|
|
rankGain:0.3, hpLoss:0.5,
|
|
count:getRandomInt(300, 800), countGrowth:getRandomInt(1, 5),
|
|
weights:{hack:0,str:0.05,def:0.05,dex:0.35,agi:0.35,cha:0.1, int:0.05},
|
|
decays:{hack:0,str:0.91,def:0.91,dex:0.91,agi:0.91,cha:0.9, int:1},
|
|
isStealth:true
|
|
});
|
|
this.contracts["Bounty Hunter"] = new Contract({
|
|
name:"Bounty Hunter",
|
|
desc:"Hunt down and capture fugitive Synthoids. These Synthoids are wanted alive.<br><br>" +
|
|
"Successfully completing a Bounty Hunter contract will lower the population in your " +
|
|
"current city, and will also increase its chaos level.",
|
|
baseDifficulty:250, difficultyFac:1.04,rewardFac:1.085,
|
|
rankGain:0.9, hpLoss:1,
|
|
count:getRandomInt(200, 750), countGrowth:getRandomInt(1, 3),
|
|
weights:{hack:0,str:0.15,def:0.15,dex:0.25,agi:0.25,cha:0.1, int:0.1},
|
|
decays:{hack:0,str:0.91,def:0.91,dex:0.91,agi:0.91,cha:0.8, int:0.9},
|
|
isKill:true
|
|
});
|
|
this.contracts["Retirement"] = new Contract({
|
|
name:"Retirement",
|
|
desc:"Hunt down and retire (kill) rogue Synthoids.<br><br>" +
|
|
"Successfully copmleting a Retirement contract will lower the population in your current " +
|
|
"city, and will also increase its chaos level.",
|
|
baseDifficulty:200, difficultyFac:1.03, rewardFac:1.065,
|
|
rankGain:0.6, hpLoss:1,
|
|
count:getRandomInt(300, 900), countGrowth:getRandomInt(1,4),
|
|
weights:{hack:0,str:0.2,def:0.2,dex:0.2,agi:0.2,cha:0.1, int:0.1},
|
|
decays:{hack:0,str:0.91,def:0.91,dex:0.91,agi:0.91,cha:0.8, int:0.9},
|
|
isKill:true
|
|
});
|
|
|
|
this.operations["Investigation"] = new Operation({
|
|
name:"Investigation",
|
|
desc:"As a field agent, investigate and identify Synthoid " +
|
|
"populations, movements, and operations.<br><br>Successful " +
|
|
"Investigation ops will increase the accuracy of your " +
|
|
"synthoid data.<br><br>" +
|
|
"You will NOT lose HP from failed Investigation ops.",
|
|
baseDifficulty:400, difficultyFac:1.03,rewardFac:1.07,reqdRank:25,
|
|
rankGain:2, rankLoss:0.2,
|
|
count:getRandomInt(50, 400), countGrowth:1,
|
|
weights:{hack:0.25,str:0.05,def:0.05,dex:0.2,agi:0.1,cha:0.25, int:0.1},
|
|
decays:{hack:0.85,str:0.9,def:0.9,dex:0.9,agi:0.9,cha:0.7, int:0.9},
|
|
isStealth:true
|
|
});
|
|
this.operations["Undercover Operation"] = new Operation({
|
|
name:"Undercover Operation",
|
|
desc:"Conduct undercover operations to identify hidden " +
|
|
"and underground Synthoid communities and organizations.<br><br>" +
|
|
"Successful Undercover ops will increase the accuracy of your synthoid " +
|
|
"data.",
|
|
baseDifficulty:500, difficultyFac:1.04, rewardFac:1.09, reqdRank:100,
|
|
rankGain:4, rankLoss:0.4, hpLoss:2,
|
|
count:getRandomInt(50, 300), countGrowth:1,
|
|
weights:{hack:0.2,str:0.05,def:0.05,dex:0.2,agi:0.2,cha:0.2, int:0.1},
|
|
decays:{hack:0.8,str:0.9,def:0.9,dex:0.9,agi:0.9,cha:0.7, int:0.9},
|
|
isStealth:true
|
|
});
|
|
this.operations["Sting Operation"] = new Operation({
|
|
name:"Sting Operation",
|
|
desc:"Conduct a sting operation to bait and capture particularly " +
|
|
"notorious Synthoid criminals.",
|
|
baseDifficulty:650, difficultyFac:1.04, rewardFac:1.095, reqdRank:500,
|
|
rankGain:5, rankLoss:0.5, hpLoss:2.5,
|
|
count:getRandomInt(25,400), countGrowth:0.75,
|
|
weights:{hack:0.25,str:0.05,def:0.05,dex:0.25,agi:0.1,cha:0.2, int:0.1},
|
|
decays:{hack:0.8,str:0.85,def:0.85,dex:0.85,agi:0.85,cha:0.7, int:0.9},
|
|
isStealth:true
|
|
});
|
|
this.operations["Raid"] = new Operation({
|
|
name:"Raid",
|
|
desc:"Lead an assault on a known Synthoid community. Note that " +
|
|
"there must be an existing Synthoid community in your current city " +
|
|
"in order for this Operation to be successful",
|
|
baseDifficulty:800, difficultyFac:1.045, rewardFac:1.1, reqdRank:3000,
|
|
rankGain:50,rankLoss:2.5,hpLoss:50,
|
|
count:getRandomInt(25, 150), countGrowth:0.2,
|
|
weights:{hack:0.1,str:0.2,def:0.2,dex:0.2,agi:0.2,cha:0, int:0.1},
|
|
decays:{hack:0.7,str:0.8,def:0.8,dex:0.8,agi:0.8,cha:0, int:0.9},
|
|
isKill:true
|
|
});
|
|
this.operations["Stealth Retirement Operation"] = new Operation({
|
|
name:"Stealth Retirement Operation",
|
|
desc:"Lead a covert operation to retire Synthoids. The " +
|
|
"objective is to complete the task without " +
|
|
"drawing any attention. Stealth and discretion are key.",
|
|
baseDifficulty:1000, difficultyFac:1.05, rewardFac:1.11, reqdRank:20e3,
|
|
rankGain:20, rankLoss:2, hpLoss:10,
|
|
count:getRandomInt(25, 250), countGrowth:0.1,
|
|
weights:{hack:0.1,str:0.1,def:0.1,dex:0.3,agi:0.3,cha:0, int:0.1},
|
|
decays:{hack:0.7,str:0.8,def:0.8,dex:0.8,agi:0.8,cha:0, int:0.9},
|
|
isStealth:true, isKill:true
|
|
});
|
|
this.operations["Assassination"] = new Operation({
|
|
name:"Assassination",
|
|
desc:"Assassinate Synthoids that have been identified as " +
|
|
"important, high-profile social and political leaders " +
|
|
"in the Synthoid communities.",
|
|
baseDifficulty:1500, difficultyFac:1.06, rewardFac:1.14, reqdRank:50e3,
|
|
rankGain:40, rankLoss:4, hpLoss:5,
|
|
count:getRandomInt(25, 200), countGrowth:0.1,
|
|
weights:{hack:0.1,str:0.1,def:0.1,dex:0.3,agi:0.3,cha:0, int:0.1},
|
|
decays:{hack:0.6,str:0.8,def:0.8,dex:0.8,agi:0.8,cha:0, int:0.8},
|
|
isStealth:true, isKill:true
|
|
});
|
|
}
|
|
|
|
Bladeburner.prototype.storeCycles = function(numCycles=1) {
|
|
this.storedCycles += numCycles;
|
|
}
|
|
|
|
Bladeburner.prototype.process = function() {
|
|
//If the Player starts doing some other actions, set action to idle and alert
|
|
if (Player.isWorking) {
|
|
if (this.action.type !== ActionTypes["Idle"]) {
|
|
dialogBoxCreate("Your Bladeburner action was cancelled because you started " +
|
|
"doing something else");
|
|
}
|
|
this.resetAction();
|
|
}
|
|
|
|
//A 'tick' for this mechanic is one second (= 5 game cycles)
|
|
if (this.storedCycles >= CyclesPerSecond) {
|
|
var seconds = Math.floor(this.storedCycles / CyclesPerSecond);
|
|
seconds = Math.min(seconds, 5); //Max of 5 'ticks'
|
|
this.storedCycles -= seconds * CyclesPerSecond;
|
|
|
|
//Stamina
|
|
this.calculateMaxStamina();
|
|
this.stamina += (this.calculateStaminaGainPerSecond() * seconds);
|
|
this.stamina = Math.min(this.maxStamina, this.stamina);
|
|
|
|
//Count increase for contracts/operations
|
|
for (var contractName in this.contracts) {
|
|
if (this.contracts.hasOwnProperty(contractName)) {
|
|
var contract = this.contracts[contractName];
|
|
contract.count += (seconds * contract.countGrowth/ActionCountGrowthPeriod);
|
|
}
|
|
}
|
|
for (var operationName in this.operations) {
|
|
if (this.operations.hasOwnProperty(operationName)) {
|
|
var op = this.operations[operationName];
|
|
op.count += (seconds * op.countGrowth/ActionCountGrowthPeriod);
|
|
}
|
|
}
|
|
|
|
//Chaos goes down very slowly
|
|
for (var i = 0; i < CityNames.length; ++i) {
|
|
var city = this.cities[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);
|
|
}
|
|
|
|
//Random Events
|
|
this.randomEventCounter -= seconds;
|
|
if (this.randomEventCounter <= 0) {
|
|
this.randomEvent();
|
|
this.randomEventCounter = getRandomInt(300, 600);
|
|
}
|
|
|
|
this.processAction(seconds);
|
|
|
|
//Automation
|
|
if (this.automateEnabled) {
|
|
if (this.stamina <= this.automateThreshLow) {
|
|
if (this.action.name !== this.automateActionLow.name || this.action.type !== this.automateActionLow.type) {
|
|
this.action = this.automateActionLow;
|
|
this.startAction(this.action);
|
|
}
|
|
} else if (this.stamina >= this.automateThreshHigh) {
|
|
if (this.action.name !== this.automateActionHigh.name || this.action.type !== this.automateActionHigh.type) {
|
|
this.action = this.automateActionHigh;
|
|
this.startAction(this.action);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (Engine.currentPage === Engine.Page.Bladeburner) {
|
|
this.updateContent();
|
|
}
|
|
}
|
|
}
|
|
|
|
Bladeburner.prototype.calculateMaxStamina = function() {
|
|
var effAgility = Player.agility * this.skillMultipliers.effAgi;
|
|
var maxStamina = (Math.pow(effAgility, 0.8) + this.staminaBonus);
|
|
maxStamina *= this.skillMultipliers.stamina;
|
|
maxStamina *= Player.bladeburner_max_stamina_mult;
|
|
if (isNaN(maxStamina)) {throw new Error("Max Stamina calculated to be NaN in Bladeburner.calculateMaxStamina()");}
|
|
this.maxStamina = maxStamina;
|
|
}
|
|
|
|
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);
|
|
return gain * (this.skillMultipliers.stamina * Player.bladeburner_stamina_gain_mult);
|
|
}
|
|
|
|
Bladeburner.prototype.calculateStaminaPenalty = function() {
|
|
return Math.min(1, this.stamina / (0.5 * this.maxStamina));
|
|
}
|
|
|
|
Bladeburner.prototype.changeRank = function(change) {
|
|
if (isNaN(change)) {throw new Error("NaN passed into Bladeburner.changeRank()");}
|
|
this.rank += change;
|
|
if (this.rank < 0) {this.rank = 0;}
|
|
this.maxRank = Math.max(this.rank, this.maxRank);
|
|
|
|
var bladeburnersFactionName = "Bladeburners";
|
|
if (factionExists(bladeburnersFactionName)) {
|
|
var bladeburnerFac = Factions[bladeburnersFactionName];
|
|
if (!(bladeburnerFac instanceof Faction)) {
|
|
throw new Error("Could not properly get Bladeburner Faction object in Bladeburner UI Overview Faction button");
|
|
}
|
|
if (bladeburnerFac.isMember) {
|
|
bladeburnerFac.playerReputation += (RankToFactionRepFactor * change * Player.faction_rep_mult);
|
|
}
|
|
}
|
|
|
|
//Gain skill points. You get 1 every 4 ranks
|
|
var rankNeededForSp = (this.totalSkillPoints+1) * RanksPerSkillPoint;
|
|
if (this.maxRank >= rankNeededForSp) {
|
|
//Calculate how many skill points to gain
|
|
var gainedSkillPoints = Math.floor((this.maxRank - rankNeededForSp) / RanksPerSkillPoint + 1);
|
|
this.skillPoints += gainedSkillPoints;
|
|
this.totalSkillPoints += gainedSkillPoints;
|
|
}
|
|
}
|
|
|
|
Bladeburner.prototype.getCurrentCity = function() {
|
|
var city = this.cities[this.city];
|
|
if (!(city instanceof City)) {
|
|
throw new Error("Bladeburner.getCurrentCity() did not properly return a City object");
|
|
}
|
|
return city;
|
|
}
|
|
|
|
Bladeburner.prototype.resetSkillMultipliers = function() {
|
|
this.skillMultipliers = {
|
|
successChanceAll:1,
|
|
successChanceStealth:1,
|
|
successChanceKill:1,
|
|
successChanceContract:1,
|
|
successChanceOperation:1,
|
|
successChanceEstimate:1,
|
|
actionTime:1,
|
|
effHack:1,
|
|
effStr:1,
|
|
effDef:1,
|
|
effDex:1,
|
|
effAgi:1,
|
|
effCha:1,
|
|
effInt:1,
|
|
stamina:1,
|
|
weaponAbility:1,
|
|
gunAbility:1,
|
|
};
|
|
}
|
|
|
|
Bladeburner.prototype.updateSkillMultipliers = function() {
|
|
this.resetSkillMultipliers();
|
|
for (var skillName in this.skills) {
|
|
if (this.skills.hasOwnProperty(skillName)) {
|
|
var skill = Skills[skillName];
|
|
if (skill == null) {
|
|
throw new Error("Could not find Skill Object for: " + skillName);
|
|
}
|
|
var level = this.skills[skillName];
|
|
if (level == null || level <= 0) {continue;} //Not upgraded
|
|
|
|
var multiplierNames = Object.keys(this.skillMultipliers);
|
|
for (var i = 0; i < multiplierNames.length; ++i) {
|
|
var multiplierName = multiplierNames[i];
|
|
if (skill[multiplierName] != null && !isNaN(skill[multiplierName])) {
|
|
var value = skill[multiplierName] * level;
|
|
var multiplierValue = 1 + (value / 100);
|
|
if (multiplierName === "actionTime") {
|
|
multiplierValue = 1 - (value / 100);
|
|
}
|
|
this.skillMultipliers[multiplierName] *= multiplierValue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Bladeburner.prototype.upgradeSkill = function(skill) {
|
|
//This does NOT handle deduction of skill points
|
|
var skillName = skill.name;
|
|
if (this.skills[skillName]) {
|
|
++this.skills[skillName];
|
|
} else {
|
|
this.skills[skillName] = 1;
|
|
}
|
|
if (isNaN(this.skills[skillName]) || this.skills[skillName] < 0) {
|
|
throw new Error("Level of Skill " + skillName + " is invalid: " + this.skills[skillName]);
|
|
}
|
|
this.updateSkillMultipliers();
|
|
}
|
|
|
|
Bladeburner.prototype.getActionObject = function(actionId) {
|
|
//Given an ActionIdentifier object, returns the corresponding
|
|
//Contract, Operation, or BlackOperation object
|
|
switch (actionId.type) {
|
|
case ActionTypes["Contract"]:
|
|
return this.contracts[actionId.name];
|
|
break;
|
|
case ActionTypes["Operation"]:
|
|
return this.operations[actionId.name];
|
|
break;
|
|
case ActionTypes["BlackOp"]:
|
|
case ActionTypes["BlackOperation"]:
|
|
return BlackOperations[actionId.name];
|
|
break;
|
|
default:
|
|
return null;
|
|
console.log("WARNING: Bladeburner.getActionObject() called with an unexpected " +
|
|
"ActionIdentifier type: " + actionId.type);
|
|
}
|
|
}
|
|
|
|
//Sets the player to the "IDLE" action
|
|
Bladeburner.prototype.resetAction = function() {
|
|
this.action = new ActionIdentifier({type:ActionTypes.Idle});
|
|
}
|
|
|
|
Bladeburner.prototype.startAction = function(actionId) {
|
|
this.action = actionId;
|
|
this.actionTimeCurrent = 0;
|
|
switch (actionId.type) {
|
|
case ActionTypes["Idle"]:
|
|
this.actionTimeToComplete = 0;
|
|
break;
|
|
case ActionTypes["Contract"]:
|
|
try {
|
|
var action = this.getActionObject(actionId);
|
|
if (action == null) {
|
|
throw new Error("Failed to get Contract Object for: " + actionId.name);
|
|
}
|
|
if (action.count < 1) {return this.resetAction();}
|
|
this.actionTimeToComplete = action.getActionTime(this);
|
|
} catch(e) {
|
|
exceptionAlert(e);
|
|
}
|
|
break;
|
|
case ActionTypes["Operation"]:
|
|
try {
|
|
var action = this.getActionObject(actionId);
|
|
if (action == null) {
|
|
throw new Error ("Failed to get Operation Object for: " + actionId.name);
|
|
}
|
|
if (action.count < 1) {return this.resetAction();}
|
|
this.actionTimeToComplete = action.getActionTime(this);
|
|
} catch(e) {
|
|
exceptionAlert(e);
|
|
}
|
|
break;
|
|
case ActionTypes["BlackOp"]:
|
|
case ActionTypes["BlackOperation"]:
|
|
try {
|
|
var action = this.getActionObject(actionId);
|
|
if (action == null) {
|
|
throw new Error("Failed to get BlackOperation object for: " + actionId.name);
|
|
}
|
|
this.actionTimeToComplete = action.getActionTime(this);
|
|
} catch(e) {
|
|
exceptionAlert(e);
|
|
}
|
|
break;
|
|
case ActionTypes["Training"]:
|
|
this.actionTimeToComplete = 30;
|
|
break;
|
|
case ActionTypes["Recruitment"]:
|
|
var effCharisma = Player.charisma * this.skillMultipliers.effCha;
|
|
var charismaFactor = Math.pow(effCharisma, 0.81) + effCharisma / 90;
|
|
var time = Math.max(10, Math.round(BaseRecruitmentTimeNeeded - charismaFactor));
|
|
this.actionTimeToComplete = time;
|
|
break;
|
|
case ActionTypes["FieldAnalysis"]:
|
|
case ActionTypes["Field Analysis"]:
|
|
this.actionTimeToComplete = 30;
|
|
break;
|
|
default:
|
|
throw new Error("Invalid Action Type in Bladeburner.startAction(): " + actionId.type);
|
|
break;
|
|
}
|
|
}
|
|
|
|
Bladeburner.prototype.processAction = function(seconds) {
|
|
if (this.action.type === ActionTypes["Idle"]) {return;}
|
|
if (this.actionTimeToComplete <= 0) {
|
|
console.log("action.type: " + this.action.type);
|
|
throw new Error("Invalid actionTimeToComplete value: " + this.actionTimeToComplete);
|
|
}
|
|
if (!(this.action instanceof ActionIdentifier)) {
|
|
throw new Error("Bladeburner.action is not an ActionIdentifier Object");
|
|
}
|
|
|
|
this.actionTimeCurrent += seconds;
|
|
if (this.actionTimeCurrent >= this.actionTimeToComplete) {
|
|
return this.completeAction();
|
|
}
|
|
}
|
|
|
|
Bladeburner.prototype.completeAction = function() {
|
|
switch (this.action.type) {
|
|
case ActionTypes["Contract"]:
|
|
case ActionTypes["Operation"]:
|
|
try {
|
|
var isOperation = (this.action.type === ActionTypes["Operation"]);
|
|
var action = this.getActionObject(this.action);
|
|
if (action == null) {
|
|
throw new Error("Failed to get Contract/Operation Object for: " + this.action.name);
|
|
}
|
|
var difficulty = action.getDifficulty();
|
|
var difficultyMultiplier = Math.pow(difficulty, DiffMultExponentialFactor) + difficulty / DiffMultLinearFactor;
|
|
var rewardMultiplier = Math.pow(action.rewardFac, action.level-1);
|
|
|
|
//Stamina loss is based on difficulty
|
|
this.stamina -= (BaseStaminaLoss * difficultyMultiplier);
|
|
if (this.stamina < 0) {this.stamina = 0;}
|
|
|
|
//Process Contract/Operation success/failure
|
|
if (action.attempt(this)) {
|
|
this.gainActionStats(action, true);
|
|
++action.successes;
|
|
--action.count;
|
|
|
|
//Earn money for contracts
|
|
var moneyGain = 0;
|
|
if (!isOperation) {
|
|
moneyGain = ContractBaseMoneyGain * rewardMultiplier;
|
|
Player.gainMoney(moneyGain);
|
|
}
|
|
|
|
if (isOperation) {
|
|
action.maxLevel = Math.floor(action.successes / OperationSuccessesPerLevel) + 1;
|
|
} else {
|
|
action.maxLevel = Math.floor(action.successes / ContractSuccessesPerLevel) + 1;
|
|
}
|
|
if (action.rankGain) {
|
|
var gain = addOffset(action.rankGain * rewardMultiplier, 10);
|
|
this.changeRank(gain);
|
|
if (isOperation && this.logging.ops) {
|
|
this.log(action.name + " successfully completed! Gained " + formatNumber(gain, 3) + " rank");
|
|
} else if (!isOperation && this.logging.contracts) {
|
|
this.log(action.name + " contract successfully completed! Gained " + formatNumber(gain, 3) + " rank and " + numeral(moneyGain).format("$0.000a"));
|
|
}
|
|
}
|
|
isOperation ? this.completeOperation(true) : this.completeContract(true);
|
|
} else {
|
|
this.gainActionStats(action, false);
|
|
++action.failures;
|
|
var loss = 0, damage = 0;
|
|
if (action.rankLoss) {
|
|
loss = addOffset(action.rankLoss * rewardMultiplier, 10);
|
|
this.changeRank(-1 * loss);
|
|
}
|
|
if (action.hpLoss) {
|
|
damage = action.hpLoss * difficultyMultiplier;
|
|
damage = Math.ceil(addOffset(damage, 10));
|
|
this.hpLost += damage;
|
|
if (Player.takeDamage(damage)) {
|
|
++this.numHosp;
|
|
this.moneyLost += (CONSTANTS.HospitalCostPerHp * Player.max_hp);
|
|
}
|
|
}
|
|
var logLossText = "";
|
|
if (loss > 0) {logLossText += "Lost " + formatNumber(loss, 3) + " rank.";}
|
|
if (damage > 0) {logLossText += "Took " + formatNumber(damage, 0) + " damage.";}
|
|
if (isOperation && this.logging.ops) {
|
|
this.log(action.name + " failed! " + logLossText);
|
|
} else if (!isOperation && this.logging.contracts) {
|
|
this.log(action.name + " contract failed! " + logLossText);
|
|
}
|
|
isOperation ? this.completeOperation(false) : this.completeContract(false);
|
|
}
|
|
if (action.autoLevel) {action.level = action.maxLevel;} //Autolevel
|
|
this.startAction(this.action); //Repeat action
|
|
} catch(e) {
|
|
exceptionAlert(e);
|
|
}
|
|
break;
|
|
case ActionTypes["BlackOp"]:
|
|
case ActionTypes["BlackOperation"]:
|
|
try {
|
|
var action = this.getActionObject(this.action);
|
|
if (action == null || !(action instanceof BlackOperation)) {
|
|
throw new Error("Failed to get BlackOperation Object for: " + this.action.name);
|
|
}
|
|
var difficulty = action.getDifficulty();
|
|
var difficultyMultiplier = Math.pow(difficulty, DiffMultExponentialFactor) + difficulty / DiffMultLinearFactor;
|
|
|
|
//Stamina loss is based on difficulty
|
|
this.stamina -= (BaseStaminaLoss * difficultyMultiplier);
|
|
if (this.stamina < 0) {this.stamina = 0;}
|
|
|
|
//Team loss variables
|
|
var teamCount = action.teamCount, teamLossMax;
|
|
|
|
if (action.attempt(this)) {
|
|
this.gainActionStats(action, true);
|
|
action.count = 0;
|
|
this.blackops[action.name] = true;
|
|
var rankGain = 0;
|
|
if (action.rankGain) {
|
|
rankGain = addOffset(action.rankGain, 10);
|
|
this.changeRank(rankGain);
|
|
}
|
|
teamLossMax = Math.ceil(teamCount/2);
|
|
|
|
//Operation Daedalus
|
|
if (action.name === "Operation Daedalus") {
|
|
this.resetAction();
|
|
return hackWorldDaemon(Player.bitNodeN);
|
|
}
|
|
|
|
if (Engine.currentPage === Engine.Page.Bladeburner) {
|
|
this.createActionAndSkillsContent();
|
|
}
|
|
|
|
if (this.logging.blackops) {
|
|
this.log(action.name + " successful! Gained " + formatNumber(rankGain, 1) + " rank");
|
|
}
|
|
} else {
|
|
this.gainActionStats(action, false);
|
|
var rankLoss = 0, damage = 0;
|
|
if (action.rankLoss) {
|
|
rankLoss = addOffset(action.rankLoss, 10);
|
|
this.changeRank(-1 * rankLoss);
|
|
}
|
|
if (action.hpLoss) {
|
|
damage = action.hpLoss * difficultyMultiplier;
|
|
damage = Math.ceil(addOffset(damage, 10));
|
|
if (Player.takeDamage(damage)) {
|
|
++this.numHosp;
|
|
this.moneyLost += (CONSTANTS.HospitalCostPerHp * Player.max_hp);
|
|
}
|
|
}
|
|
teamLossMax = Math.floor(teamCount);
|
|
|
|
if (this.logging.blackops) {
|
|
this.log(action.name + " failed! Lost " + formatNumber(rankLoss, 1) + " rank and took" + formatNumber(damage, 0) + " damage");
|
|
}
|
|
}
|
|
|
|
this.resetAction(); //Stop regardless of success or fail
|
|
|
|
//Calculate team lossses
|
|
if (teamCount >= 1) {
|
|
var losses = getRandomInt(1, teamLossMax);
|
|
this.teamSize -= losses;
|
|
this.teamLost += losses;
|
|
if (this.logging.blackops) {
|
|
this.log("You lost " + formatNumber(losses, 0) + " team members during " + action.name);
|
|
}
|
|
}
|
|
} catch(e) {
|
|
exceptionAlert(e);
|
|
}
|
|
break;
|
|
case ActionTypes["Training"]:
|
|
this.stamina -= (0.5 * BaseStaminaLoss);
|
|
var strExpGain = 30 * Player.strength_exp_mult,
|
|
defExpGain = 30 * Player.defense_exp_mult,
|
|
dexExpGain = 30 * Player.dexterity_exp_mult,
|
|
agiExpGain = 30 * Player.agility_exp_mult,
|
|
staminaGain = 0.04 * this.skillMultipliers.stamina;
|
|
Player.gainStrengthExp(strExpGain);
|
|
Player.gainDefenseExp(defExpGain);
|
|
Player.gainDexterityExp(dexExpGain);
|
|
Player.gainAgilityExp(agiExpGain);
|
|
this.staminaBonus += (staminaGain);
|
|
if (this.logging.general) {
|
|
this.log("Training completed. Gained: " +
|
|
formatNumber(strExpGain, 1) + " str exp, " +
|
|
formatNumber(defExpGain, 1) + " def exp, " +
|
|
formatNumber(dexExpGain, 1) + " dex exp, " +
|
|
formatNumber(agiExpGain, 1) + " agi exp, " +
|
|
formatNumber(staminaGain, 3) + " max stamina");
|
|
}
|
|
this.startAction(this.action); //Repeat action
|
|
break;
|
|
case ActionTypes["FieldAnalysis"]:
|
|
case ActionTypes["Field Analysis"]:
|
|
//Does not use stamina. Effectiveness depends on hacking, int, and cha
|
|
var eff = 0.04 * Math.pow(Player.hacking_skill, 0.3) +
|
|
0.04 * Math.pow(Player.intelligence, 0.9) +
|
|
0.02 * Math.pow(Player.charisma, 0.3);
|
|
eff *= Player.bladeburner_analysis_mult;
|
|
if (isNaN(eff) || eff < 0) {
|
|
throw new Error("Field Analysis Effectiveness calculated to be NaN or negative");
|
|
}
|
|
var hackingExpGain = 20 * Player.hacking_exp_mult,
|
|
charismaExpGain = 20 * Player.charisma_exp_mult;
|
|
Player.gainHackingExp(hackingExpGain);
|
|
Player.gainIntelligenceExp(BaseIntGain);
|
|
Player.gainCharismaExp(charismaExpGain);
|
|
this.changeRank(0.1);
|
|
console.log("DEBUG: Field Analysis effectiveness is " + (eff * this.skillMultipliers.successChanceEstimate));
|
|
this.getCurrentCity().improvePopulationEstimateByPercentage(eff * this.skillMultipliers.successChanceEstimate);
|
|
if (this.logging.general) {
|
|
this.log("Field analysis completed. Gained 0.1 rank, " + formatNumber(hackingExpGain, 1) + " hacking exp, and " + formatNumber(charismaExpGain, 1) + " charisma exp");
|
|
}
|
|
this.startAction(this.action); //Repeat action
|
|
break;
|
|
case ActionTypes["Recruitment"]:
|
|
var successChance = Math.pow(Player.charisma, 0.45) / (this.teamSize + 1);
|
|
console.log("Bladeburner recruitment success chance: " + successChance);
|
|
if (Math.random() < successChance) {
|
|
var expGain = 2 * 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;
|
|
Player.gainCharismaExp(expGain);
|
|
if (this.logging.general) {
|
|
this.log("Failed to recruit a team member. Gained " + formatNumber(expGain, 1) + " charisma exp");
|
|
}
|
|
}
|
|
this.startAction(this.action); //Repeat action
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
Bladeburner.prototype.completeContract = function(success) {
|
|
if (this.action.type !== ActionTypes.Contract) {
|
|
throw new Error("completeContract() called even though current action is not a Contract");
|
|
}
|
|
var city = this.getCurrentCity();
|
|
if (success) {
|
|
switch (this.action.name) {
|
|
case "Tracking":
|
|
//Increase estimate accuracy by a relatively small amount
|
|
city.improvePopulationEstimateByCount(getRandomInt(100, 1e3));
|
|
break;
|
|
case "Bounty Hunter":
|
|
city.changePopulationByCount(-1, {estChange:-1});
|
|
city.changeChaosByCount(0.02);
|
|
break;
|
|
case "Retirement":
|
|
city.changePopulationByCount(-1, {estChange:-1});
|
|
city.changeChaosByCount(0.04);
|
|
break;
|
|
default:
|
|
throw new Error("Invalid Action name in completeContract: " + this.action.name);
|
|
}
|
|
}
|
|
}
|
|
|
|
Bladeburner.prototype.completeOperation = function(success) {
|
|
if (this.action.type !== ActionTypes.Operation) {
|
|
throw new Error("completeOperation() called even though current action is not an Operation");
|
|
}
|
|
var action = this.getActionObject(this.action);
|
|
if (action == null) {
|
|
throw new Error("Failed to get Contract/Operation Object for: " + this.action.name);
|
|
}
|
|
|
|
//Calculate team losses
|
|
var teamCount = action.teamCount, max;
|
|
if (teamCount >= 1) {
|
|
if (success) {
|
|
max = Math.ceil(teamCount/2);
|
|
} else {
|
|
max = Math.floor(teamCount)
|
|
}
|
|
var losses = getRandomInt(0, max);
|
|
this.teamSize -= losses;
|
|
this.teamLost += losses;
|
|
if (this.logging.ops && losses > 0) {
|
|
this.log("Lost " + formatNumber(losses, 0) + " team members during this " + action.name);
|
|
}
|
|
}
|
|
|
|
var city = this.getCurrentCity();
|
|
if (this.logging.ops) {
|
|
if (success) {
|
|
this.log(action.name + " completed successfully! ")
|
|
} else {
|
|
|
|
}
|
|
}
|
|
switch (action.name) {
|
|
case "Investigation":
|
|
if (success) {
|
|
city.improvePopulationEstimateByPercentage(0.4 * this.skillMultipliers.successChanceEstimate);
|
|
if (Math.random() < (0.02 * this.skillMultipliers.successChanceEstimate)) {
|
|
city.improveCommunityEstimate(1);
|
|
}
|
|
} else {
|
|
this.triggerPotentialMigration(this.city, 0.1);
|
|
}
|
|
break;
|
|
case "Undercover Operation":
|
|
if (success) {
|
|
city.improvePopulationEstimateByPercentage(0.8 * this.skillMultipliers.successChanceEstimate);
|
|
if (Math.random() < (0.02 * this.skillMultipliers.successChanceEstimate)) {
|
|
city.improveCommunityEstimate(1);
|
|
}
|
|
} else {
|
|
this.triggerPotentialMigration(this.city, 0.15);
|
|
}
|
|
break;
|
|
case "Sting Operation":
|
|
if (success) {
|
|
city.changePopulationByPercentage(-0.1, {changeEstEqually:true, nonZero:true});
|
|
}
|
|
city.changeChaosByCount(0.1);
|
|
break;
|
|
case "Raid":
|
|
if (success) {
|
|
city.changePopulationByPercentage(-1, {changeEstEqually:true, nonZero:true});
|
|
--city.comms;
|
|
--city.commsEst;
|
|
} else {
|
|
var change = getRandomInt(-3, -1);
|
|
city.changePopulationByPercentage(change, {nonZero:true});
|
|
}
|
|
city.changeChaosByPercentage(getRandomInt(1, 5));
|
|
break;
|
|
case "Stealth Retirement Operation":
|
|
if (success) {
|
|
city.changePopulationByPercentage(-0.5, {changeEstEqually:true,nonZero:true});
|
|
}
|
|
city.changeChaosByPercentage(getRandomInt(-3, -1));
|
|
break;
|
|
case "Assassination":
|
|
if (success) {
|
|
city.changePopulationByCount(-1, {estChange:-1});
|
|
}
|
|
city.changeChaosByPercentage(getRandomInt(-5, 5));
|
|
break;
|
|
default:
|
|
throw new Error("Invalid Action name in completeOperation: " + this.action.name);
|
|
}
|
|
}
|
|
|
|
//Process stat gains from Contracts, Operations, and Black Operations
|
|
//@action(Action obj) - Derived action class
|
|
//@success(bool) - Whether action was successful
|
|
Bladeburner.prototype.gainActionStats = function(action, success) {
|
|
var difficulty = action.getDifficulty();
|
|
|
|
//Gain multiplier based on difficulty. If this changes then the
|
|
//same variable calculated in completeAction() needs to change too
|
|
var difficultyMult = Math.pow(difficulty, 0.21);
|
|
|
|
var time = this.actionTimeToComplete;
|
|
var successMult = success ? 1 : 0.5;
|
|
|
|
var unweightedGain = time * BaseStatGain * successMult * difficultyMult;
|
|
var unweightedIntGain = time * BaseIntGain * successMult * difficultyMult;
|
|
Player.gainHackingExp(unweightedGain * action.weights.hack * Player.hacking_exp_mult);
|
|
Player.gainStrengthExp(unweightedGain * action.weights.str * Player.strength_exp_mult);
|
|
Player.gainDefenseExp(unweightedGain * action.weights.def * Player.defense_exp_mult);
|
|
Player.gainDexterityExp(unweightedGain * action.weights.dex * Player.dexterity_exp_mult);
|
|
Player.gainAgilityExp(unweightedGain * action.weights.agi * Player.agility_exp_mult);
|
|
Player.gainCharismaExp(unweightedGain * action.weights.cha * Player.charisma_exp_mult);
|
|
Player.gainIntelligenceExp(unweightedIntGain * action.weights.int);
|
|
}
|
|
|
|
Bladeburner.prototype.randomEvent = function() {
|
|
var chance = Math.random();
|
|
|
|
//Choose random source/destination city for events
|
|
var sourceCityName = 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)];
|
|
while (destCityName === sourceCityName) {
|
|
destCityName = CityNames[getRandomInt(0, 5)];
|
|
}
|
|
var destCity = this.cities[destCityName];
|
|
|
|
if (!(sourceCity instanceof City) || !(destCity instanceof City)) {
|
|
throw new Error("sourceCity was not a City object in Bladeburner.randomEvent()");
|
|
}
|
|
|
|
if (chance <= 0.05) {
|
|
//New Synthoid Community, 5%
|
|
++sourceCity.comms;
|
|
var percentage = getRandomInt(10, 20) / 100;
|
|
var count = Math.round(sourceCity.pop * percentage);
|
|
sourceCity.pop += count;
|
|
if (this.logging.events) {
|
|
this.log("Intelligence indicates that a new Synthoid community was formed in a city");
|
|
}
|
|
} else if (chance <= 0.1) {
|
|
//Synthoid Community Migration, 5%
|
|
if (sourceCity.comms <= 0) {
|
|
//If no comms in source city, then instead trigger a new Synthoid community event
|
|
++sourceCity.comms;
|
|
var percentage = getRandomInt(10, 20) / 100;
|
|
var count = Math.round(sourceCity.pop * percentage);
|
|
sourceCity.pop += count;
|
|
if (this.logging.events) {
|
|
this.log("Intelligence indicates that a new Synthoid community was formed in a city");
|
|
}
|
|
} else {
|
|
--sourceCity.comms;
|
|
++destCity.comms;
|
|
|
|
//Change pop
|
|
var percentage = getRandomInt(10, 20) / 100;
|
|
var count = Math.round(sourceCity.pop * percentage);
|
|
sourceCity.pop -= count;
|
|
destCity.pop += count;
|
|
|
|
if (this.logging.events) {
|
|
this.log("Intelligence indicates that a Synthoid community migrated from " + sourceCityName + " to some other city");
|
|
}
|
|
}
|
|
} else if (chance <= 0.3) {
|
|
//New Synthoids (non community), 20%
|
|
var percentage = getRandomInt(8, 24) / 100;
|
|
var count = Math.round(sourceCity.pop * percentage);
|
|
sourceCity.pop += count;
|
|
if (this.logging.events) {
|
|
this.log("Intelligence indicates that the Synthoid population of " + sourceCityName + " just changed significantly");
|
|
}
|
|
} else if (chance <= 0.5) {
|
|
//Synthoid migration (non community) 20%
|
|
this.triggerMigration(sourceCityName);
|
|
if (this.logging.events) {
|
|
this.log("Intelligence indicates that a large number of Synthoids migrated from " + sourceCityName + " to some other city");
|
|
}
|
|
} else if (chance <= 0.7) {
|
|
//Synthoid Riots (+chaos), 20%
|
|
sourceCity.chaos += 1;
|
|
sourceCity.chaos *= (1 + getRandomInt(5, 10) / 100);
|
|
if (this.logging.events) {
|
|
this.log("Tensions between Synthoids and humans lead to riots in " + sourceCityName + "! Chaos increased");
|
|
}
|
|
} else if (chance <= 0.9) {
|
|
//Less Synthoids, 20%
|
|
var percentage = getRandomInt(5, 20) / 100;
|
|
var count = Math.round(sourceCity.pop * percentage);
|
|
sourceCity.pop -= count;
|
|
if (this.logging.events) {
|
|
this.log("Intelligence indicates that the Synthoid population of " + sourceCityName + " just changed significantly");
|
|
}
|
|
}
|
|
//20% chance of nothing happening
|
|
}
|
|
|
|
Bladeburner.prototype.triggerPotentialMigration = function(sourceCityName, chance) {
|
|
if (chance == null || isNaN(chance)) {
|
|
console.log("ERROR: Invalid 'chance' parameter passed into Bladeburner.triggerPotentialMigration()");
|
|
}
|
|
if (chance > 1) {chance /= 100;}
|
|
if (Math.random() < chance) {this.triggerMigration(sourceCityName);}
|
|
}
|
|
|
|
Bladeburner.prototype.triggerMigration = function(sourceCityName) {
|
|
var destCityName = CityNames[getRandomInt(0, 5)];
|
|
while (destCityName === sourceCityName) {
|
|
destCityName = CityNames[getRandomInt(0, 5)];
|
|
}
|
|
var destCity = this.cities[destCityName];
|
|
var sourceCity = this.cities[sourceCityName];
|
|
if (destCity == null || sourceCity == null) {
|
|
throw new Error("Failed to find City with name: " + destCityName);
|
|
}
|
|
var rand = Math.random(), percentage = getRandomInt(3, 15) / 100;
|
|
|
|
if (rand < 0.05 && sourceCity.comms > 0) { //5% chance for community migration
|
|
percentage *= getRandomInt(2, 4); //Migration increases population change
|
|
--sourceCity.comms;
|
|
++destCity.comms;
|
|
}
|
|
var count = Math.round(sourceCity.pop * percentage);
|
|
sourceCity.pop -= count;
|
|
destCity.pop += count;
|
|
}
|
|
|
|
var DomElems = {};
|
|
|
|
Bladeburner.prototype.initializeDomElementRefs = function() {
|
|
DomElems = {
|
|
bladeburnerDiv: null,
|
|
|
|
//Main Divs
|
|
overviewConsoleParentDiv: null,
|
|
|
|
overviewDiv: null, //Overview of stats that stays fixed on left
|
|
actionAndSkillsDiv: null, //Panel for different sections (contracts, ops, skills)
|
|
currentTab: null, //Contracts, Operations, Black Ops, Skills
|
|
|
|
consoleDiv: null,
|
|
consoleTable: null,
|
|
consoleInputRow: null, //tr
|
|
consoleInputCell: null, //td
|
|
consoleInputHeader: null, //"> "
|
|
consoleInput: null, //Actual input element
|
|
|
|
//Overview Content
|
|
overviewRank: null,
|
|
overviewStamina: null,
|
|
overviewStaminaHelpTip: null,
|
|
overviewGen1: null, //Stamina Penalty, Team, Hospitalized stats, current city
|
|
overviewEstPop: null,
|
|
overviewEstPopHelpTip: null,
|
|
overviewEstComms: null,
|
|
overviewChaos: null,
|
|
overviewSkillPoints: null,
|
|
overviewAugSuccessMult: null,
|
|
overviewAugMaxStaminaMult: null,
|
|
overviewAugStaminaGainMult: null,
|
|
overviewAugAnalysisMult: null,
|
|
|
|
//Actions and Skills Content
|
|
actionsAndSkillsDesc: null,
|
|
actionsAndSkillsList: null, //ul element of all UI elements in this panel
|
|
generalActions: {},
|
|
contracts: {},
|
|
operations: {},
|
|
blackops: {},
|
|
skills: {},
|
|
};
|
|
}
|
|
|
|
Bladeburner.prototype.createContent = function() {
|
|
DomElems.bladeburnerDiv = createElement("div", {
|
|
id:"bladeburner-container", position:"fixed", class:"generic-menupage-container",
|
|
});
|
|
|
|
//Parent Div for Overview and Console
|
|
DomElems.overviewConsoleParentDiv = createElement("div", {
|
|
height:"60%", display:"block", position:"relative",
|
|
});
|
|
|
|
//Overview and Action/Skill pane
|
|
DomElems.overviewDiv = createElement("div", {
|
|
width:"30%", display:"inline-block", border:"1px solid white",
|
|
});
|
|
|
|
DomElems.actionAndSkillsDiv = createElement("div", {
|
|
height:"60%", width:"70%", display:"block",
|
|
border:"1px solid white", margin:"6px", padding:"6px",
|
|
});
|
|
|
|
DomElems.currentTab = "general";
|
|
|
|
this.createOverviewContent();
|
|
this.createActionAndSkillsContent();
|
|
|
|
//Console
|
|
DomElems.consoleDiv = createElement("div", {
|
|
class:"bladeburner-console-div",
|
|
clickListener:()=>{
|
|
if (DomElems.consoleInput instanceof Element) {
|
|
DomElems.consoleInput.focus();
|
|
}
|
|
return false;
|
|
}
|
|
});
|
|
DomElems.consoleTable = createElement("table", {class:"bladeburner-console-table"});
|
|
DomElems.consoleInputRow = createElement("tr", {class:"bladeburner-console-input-row", id:"bladeubrner-console-input-row"});
|
|
DomElems.consoleInputCell = createElement("td", {class:"bladeburner-console-input-cell"});
|
|
DomElems.consoleInputHeader = createElement("pre", {innerText:"> "});
|
|
DomElems.consoleInput = createElement("input", {
|
|
type:"text", class:"bladeburner-console-input", tabIndex:1,
|
|
onfocus:()=>{DomElems.consoleInput.value = DomElems.consoleInput.value}
|
|
});
|
|
|
|
DomElems.consoleInputCell.appendChild(DomElems.consoleInputHeader);
|
|
DomElems.consoleInputCell.appendChild(DomElems.consoleInput);
|
|
DomElems.consoleInputRow.appendChild(DomElems.consoleInputCell);
|
|
DomElems.consoleTable.appendChild(DomElems.consoleInputRow);
|
|
DomElems.consoleDiv.appendChild(DomElems.consoleTable);
|
|
|
|
DomElems.overviewConsoleParentDiv.appendChild(DomElems.overviewDiv);
|
|
DomElems.overviewConsoleParentDiv.appendChild(DomElems.consoleDiv);
|
|
DomElems.bladeburnerDiv.appendChild(DomElems.overviewConsoleParentDiv);
|
|
DomElems.bladeburnerDiv.appendChild(DomElems.actionAndSkillsDiv);
|
|
|
|
document.getElementById("entire-game-container").appendChild(DomElems.bladeburnerDiv);
|
|
|
|
this.postToConsole("Bladeburner Console BETA");
|
|
this.postToConsole("Type 'help' to see console commands");
|
|
DomElems.consoleInput.focus();
|
|
}
|
|
|
|
Bladeburner.prototype.clearContent = function() {
|
|
if (DomElems.bladeburnerDiv instanceof Element) {
|
|
removeChildrenFromElement(DomElems.bladeburnerDiv);
|
|
removeElement(DomElems.bladeburnerDiv);
|
|
}
|
|
clearObject(DomElems);
|
|
this.initializeDomElementRefs();
|
|
}
|
|
|
|
Bladeburner.prototype.createOverviewContent = function() {
|
|
if (DomElems.overviewDiv == null) {
|
|
throw new Error("Bladeburner.createOverviewContent() called with DomElems.overviewDiv = null");
|
|
}
|
|
|
|
DomElems.overviewRank = createElement("p", {
|
|
innerText:"Rank: ",
|
|
display:"inline-block",
|
|
tooltip:"Your rank within the Bladeburner division",
|
|
});
|
|
|
|
DomElems.overviewStamina = createElement("p", {
|
|
display:"inline-block",
|
|
});
|
|
|
|
DomElems.overviewStaminaHelpTip = createElement("div", {
|
|
innerText:"?", class:"help-tip",
|
|
clickListener:()=>{
|
|
dialogBoxCreate("Performing actions will use up your stamina.<br><br>" +
|
|
"Your max stamina is determined primarily by your agility stat.<br><br>" +
|
|
"Your stamina gain rate is determined by both your agility and your " +
|
|
"max stamina. Higher max stamina leads to a higher gain rate.<br><br>" +
|
|
"Once your " +
|
|
"stamina falls below 50% of its max value, it begins to negatively " +
|
|
"affect the success rate of your contracts/operations. This penalty " +
|
|
"is shown in the overview panel. If the penalty is 15%, then this means " +
|
|
"your success rate would be multipled by 85% (100 - 15).<br><br>" +
|
|
"Your max stamina and stamina gain rate can also be increased by " +
|
|
"training, or through skills and Augmentation upgrades.");
|
|
}
|
|
});
|
|
|
|
DomElems.overviewGen1 = createElement("p", {
|
|
display:"block",
|
|
});
|
|
|
|
DomElems.overviewEstPop = createElement("p", {
|
|
innerText:"Est. Synthoid Population: ",
|
|
display:"inline-block",
|
|
tooltip:"This is your Bladeburner division's estimate of how many Synthoids exist " +
|
|
"in your current city."
|
|
});
|
|
|
|
DomElems.overviewEstPopHelpTip = createElement("div", {
|
|
innerText:"?", class:"help-tip",
|
|
clickListener:()=>{
|
|
dialogBoxCreate("The success rate of your contracts/operations depends on " +
|
|
"the population of Synthoids in your current city. " +
|
|
"The success rate that is shown to you is only an estimate, " +
|
|
"and it is based on your Synthoid population estimate.<br><br>" +
|
|
"Therefore, it is important that this Synthoid population estimate " +
|
|
"is accurate so that you have a better idea of your " +
|
|
"success rate for contracts/operations. Certain " +
|
|
"actions will increase the accuracy of your population " +
|
|
"estimate.<br><br>" +
|
|
"The Synthoid populations of cities can change due to your " +
|
|
"actions or random events. If random events occur, they will " +
|
|
"be logged in the Bladeburner Console.");
|
|
}
|
|
});
|
|
|
|
DomElems.overviewEstComms = createElement("p", {
|
|
innerText:"Est. Synthoid Communities: ",
|
|
display:"inline-block",
|
|
tooltip:"This is your Bladeburner divison's estimate of how many Synthoid " +
|
|
"communities exist in your current city.",
|
|
});
|
|
|
|
DomElems.overviewChaos = createElement("p", {
|
|
innerText:"City Chaos: ",
|
|
display:"inline-block",
|
|
tooltip:"The city's chaos level due to tensions and conflicts between humans and Synthoids. " +
|
|
"Having too high of a chaos level can make contracts and operations harder."
|
|
});
|
|
|
|
DomElems.overviewSkillPoints = createElement("p", {display:"block"});
|
|
|
|
DomElems.overviewAugSuccessMult = createElement("p", {display:"block"});
|
|
DomElems.overviewAugMaxStaminaMult = createElement("p", {display:"block"});
|
|
DomElems.overviewAugStaminaGainMult = createElement("p", {display:"block"});
|
|
DomElems.overviewAugAnalysisMult = createElement("p", {display:"block"});
|
|
|
|
|
|
DomElems.overviewDiv.appendChild(DomElems.overviewRank);
|
|
appendLineBreaks(DomElems.overviewDiv, 1);
|
|
DomElems.overviewDiv.appendChild(DomElems.overviewStamina);
|
|
DomElems.overviewDiv.appendChild(DomElems.overviewStaminaHelpTip);
|
|
DomElems.overviewDiv.appendChild(DomElems.overviewGen1);
|
|
DomElems.overviewDiv.appendChild(DomElems.overviewEstPop);
|
|
DomElems.overviewDiv.appendChild(DomElems.overviewEstPopHelpTip);
|
|
appendLineBreaks(DomElems.overviewDiv, 1);
|
|
DomElems.overviewDiv.appendChild(DomElems.overviewEstComms);
|
|
appendLineBreaks(DomElems.overviewDiv, 1);
|
|
DomElems.overviewDiv.appendChild(DomElems.overviewChaos);
|
|
appendLineBreaks(DomElems.overviewDiv, 2);
|
|
DomElems.overviewDiv.appendChild(DomElems.overviewSkillPoints);
|
|
appendLineBreaks(DomElems.overviewDiv, 1);
|
|
DomElems.overviewDiv.appendChild(DomElems.overviewAugSuccessMult);
|
|
DomElems.overviewDiv.appendChild(DomElems.overviewAugMaxStaminaMult);
|
|
DomElems.overviewDiv.appendChild(DomElems.overviewAugStaminaGainMult);
|
|
DomElems.overviewDiv.appendChild(DomElems.overviewAugAnalysisMult);
|
|
|
|
//Travel to new city button
|
|
appendLineBreaks(DomElems.overviewDiv, 1);
|
|
DomElems.overviewDiv.appendChild(createElement("a", {
|
|
innerHTML:"Travel", class:"a-link-button", display:"inline-block",
|
|
clickListener:()=>{
|
|
var popupId = "bladeburner-travel-popup-cancel-btn";
|
|
var popupArguments = [];
|
|
popupArguments.push(createElement("a", { //Cancel Button
|
|
innerText:"Cancel", class:"a-link-button",
|
|
clickListener:()=>{
|
|
removeElementById(popupId); return false;
|
|
}
|
|
}))
|
|
popupArguments.push(createElement("p", { //Info Text
|
|
innerText:"Travel to a different city for your Bladeburner " +
|
|
"activities. This does not cost any money. The city you are " +
|
|
"in for your Bladeburner duties does not affect " +
|
|
"your location in the game otherwise",
|
|
}));
|
|
for (var i = 0; i < CityNames.length; ++i) {
|
|
(function(inst, i) {
|
|
popupArguments.push(createElement("div", {
|
|
//Reusing this css class...it adds a border and makes it
|
|
//so that background color changes when you hover
|
|
class:"cmpy-mgmt-find-employee-option",
|
|
innerText:CityNames[i],
|
|
clickListener:()=>{
|
|
inst.city = CityNames[i];
|
|
removeElementById(popupId);
|
|
inst.updateOverviewContent();
|
|
return false;
|
|
}
|
|
}));
|
|
})(this, i);
|
|
}
|
|
createPopup(popupId, popupArguments);
|
|
}
|
|
}));
|
|
|
|
//Faction button
|
|
var bladeburnersFactionName = "Bladeburners";
|
|
if (factionExists(bladeburnersFactionName)) {
|
|
var bladeburnerFac = Factions[bladeburnersFactionName];
|
|
if (!(bladeburnerFac instanceof Faction)) {
|
|
throw new Error("Could not properly get Bladeburner Faction object in Bladeburner UI Overview Faction button");
|
|
}
|
|
DomElems.overviewDiv.appendChild(createElement("a", {
|
|
innerText:"Faction", class:"a-link-button", display:"inline-block",
|
|
tooltip:"Apply to the Bladeburner Faction, or go to the faction page if you are already a member",
|
|
clickListener:()=>{
|
|
if (bladeburnerFac.isMember) {
|
|
Engine.loadFactionContent();
|
|
displayFactionContent(bladeburnersFactionName);
|
|
} else {
|
|
if (this.rank >= 25) {
|
|
joinFaction(bladeburnerFac);
|
|
dialogBoxCreate("Congratulations! You were accepted into the Bladeburners faction");
|
|
removeChildrenFromElement(DomElems.overviewDiv);
|
|
this.createOverviewContent();
|
|
} else {
|
|
dialogBoxCreate("You need a rank of 25 to join the Bladeburners Faction!")
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
}));
|
|
}
|
|
|
|
DomElems.overviewDiv.appendChild(createElement("br"));
|
|
DomElems.overviewDiv.appendChild(createElement("br"));
|
|
|
|
this.updateOverviewContent();
|
|
}
|
|
|
|
Bladeburner.prototype.createActionAndSkillsContent = function() {
|
|
if (DomElems.currentTab == null) {DomElems.currentTab = "general";}
|
|
|
|
removeChildrenFromElement(DomElems.actionAndSkillsDiv);
|
|
clearObject(DomElems.generalActions);
|
|
clearObject(DomElems.contracts);
|
|
clearObject(DomElems.operations);
|
|
clearObject(DomElems.blackops);
|
|
clearObject(DomElems.skills);
|
|
|
|
//Navigation buttons
|
|
var currTab = DomElems.currentTab.toLowerCase();
|
|
var buttons = ["General", "Contracts", "Operations", "BlackOps", "Skills"];
|
|
for (var i = 0; i < buttons.length; ++i) {
|
|
(function(buttons, i, inst, currTab) {
|
|
|
|
DomElems.actionAndSkillsDiv.appendChild(createElement("a", {
|
|
innerText:buttons[i],
|
|
class:currTab === buttons[i].toLowerCase() ? "bladeburner-nav-button-inactive" : "bladeburner-nav-button",
|
|
clickListener:()=>{
|
|
DomElems.currentTab = buttons[i].toLowerCase();
|
|
inst.createActionAndSkillsContent();
|
|
return false;
|
|
}
|
|
}));
|
|
}) (buttons, i, this, currTab);
|
|
}
|
|
|
|
//General info/description for each action
|
|
DomElems.actionsAndSkillsDesc = createElement("p", {
|
|
display:"block", margin:"4px", padding:"4px"
|
|
});
|
|
|
|
//List for actions/skills
|
|
removeChildrenFromElement(DomElems.actionsAndSkillsList);
|
|
DomElems.actionsAndSkillsList = createElement("ul");
|
|
|
|
switch(currTab) {
|
|
case "general":
|
|
this.createGeneralActionsContent();
|
|
break;
|
|
case "contracts":
|
|
this.createContractsContent();
|
|
break;
|
|
case "operations":
|
|
this.createOperationsContent();
|
|
break;
|
|
case "blackops":
|
|
this.createBlackOpsContent();
|
|
break;
|
|
case "skills":
|
|
this.createSkillsContent();
|
|
break;
|
|
default:
|
|
throw new Error("Invalid value for DomElems.currentTab in Bladeburner.createActionAndSkillsContent");
|
|
}
|
|
this.updateContent();
|
|
|
|
DomElems.actionAndSkillsDiv.appendChild(DomElems.actionsAndSkillsDesc);
|
|
DomElems.actionAndSkillsDiv.appendChild(DomElems.actionsAndSkillsList);
|
|
}
|
|
|
|
Bladeburner.prototype.createGeneralActionsContent = function() {
|
|
if (DomElems.actionsAndSkillsList == null || DomElems.actionsAndSkillsDesc == null) {
|
|
throw new Error("Bladeburner.createGeneralActionsContent called with either " +
|
|
"DomElems.actionsAndSkillsList or DomElems.actionsAndSkillsDesc = null");
|
|
}
|
|
|
|
DomElems.actionsAndSkillsDesc.innerText =
|
|
"These are generic actions that will assist you in your Bladeburner " +
|
|
"duties. They will not affect your Bladeburner rank in any way."
|
|
|
|
for (var actionName in GeneralActions) {
|
|
if (GeneralActions.hasOwnProperty(actionName)) {
|
|
DomElems.generalActions[actionName] = createElement("div", {
|
|
class:"bladeburner-action", name:actionName
|
|
});
|
|
DomElems.actionsAndSkillsList.appendChild(DomElems.generalActions[actionName]);
|
|
}
|
|
}
|
|
}
|
|
|
|
Bladeburner.prototype.createContractsContent = function() {
|
|
if (DomElems.actionsAndSkillsList == null || DomElems.actionsAndSkillsDesc == null) {
|
|
throw new Error("Bladeburner.createContractsContent called with either " +
|
|
"DomElems.actionsAndSkillsList or DomElems.actionsAndSkillsDesc = null");
|
|
}
|
|
|
|
DomElems.actionsAndSkillsDesc.innerHTML =
|
|
"Complete contracts in order to increase your Bitburner rank and earn money. " +
|
|
"Failing a contract will cause you to lose HP, which can lead to hospitalization.<br><br>" +
|
|
"You can unlock higher-level contracts by successfully completing them. " +
|
|
"Higher-level contracts are more difficult, but grant more rank, experience, and money.";
|
|
|
|
for (var contractName in this.contracts) {
|
|
if (this.contracts.hasOwnProperty(contractName)) {
|
|
DomElems.contracts[contractName] = createElement("div", {
|
|
class:"bladeburner-action", name:contractName
|
|
});
|
|
DomElems.actionsAndSkillsList.appendChild(DomElems.contracts[contractName]);
|
|
}
|
|
}
|
|
}
|
|
|
|
Bladeburner.prototype.createOperationsContent = function() {
|
|
if (DomElems.actionsAndSkillsList == null || DomElems.actionsAndSkillsDesc == null) {
|
|
throw new Error("Bladeburner.createOperationsContent called with either " +
|
|
"DomElems.actionsAndSkillsList or DomElems.actionsAndSkillsDesc = null");
|
|
}
|
|
|
|
DomElems.actionsAndSkillsDesc.innerHTML =
|
|
"Carry out operations for the Bladeburner division. " +
|
|
"Failing an operation will reduce your Bladeburner rank. It will also " +
|
|
"cause you to lose HP, which can lead to hospitalization. In general, " +
|
|
"operations are harder and more punishing than contracts, " +
|
|
"but are also more rewarding.<br><br>" +
|
|
"Operations can affect the chaos level and Synthoid population of your " +
|
|
"current city. The exact effects vary between different Operations.<br><br>" +
|
|
"For operations, you can use a team. You must first recruit team members. " +
|
|
"Having a larger team will improves your chances of success.<br><br>" +
|
|
"You can unlock higher-level operations by successfully completing them. " +
|
|
"Higher-level operations are more difficult, but grant more rank and experience.";
|
|
|
|
for (var operationName in this.operations) {
|
|
if (this.operations.hasOwnProperty(operationName)) {
|
|
DomElems.operations[operationName] = createElement("div", {
|
|
class:"bladeburner-action", name:operationName
|
|
});
|
|
DomElems.actionsAndSkillsList.appendChild(DomElems.operations[operationName]);
|
|
}
|
|
}
|
|
}
|
|
|
|
Bladeburner.prototype.createBlackOpsContent = function() {
|
|
if (DomElems.actionsAndSkillsList == null || DomElems.actionsAndSkillsDesc == null) {
|
|
throw new Error("Bladeburner.createBlackOpsContent called with either " +
|
|
"DomElems.actionsAndSkillsList or DomElems.actionsAndSkillsDesc = null");
|
|
}
|
|
|
|
DomElems.actionsAndSkillsDesc.innerHTML =
|
|
"Black Operations (Black Ops) are special, one-time covert operations. " +
|
|
"Each Black Op must be unlocked successively by completing " +
|
|
"the one before it.<br><br>" +
|
|
"Like normal operations, you may use a team for Black Ops. Failing " +
|
|
"a black op will incur heavy HP and rank losses.";
|
|
|
|
//Put Black Operations in sequence of required rank
|
|
var blackops = [];
|
|
for (var blackopName in BlackOperations) {
|
|
if (BlackOperations.hasOwnProperty(blackopName)) {
|
|
blackops.push(BlackOperations[blackopName]);
|
|
}
|
|
}
|
|
blackops.sort(function(a, b) {
|
|
return (a.reqdRank - b.reqdRank);
|
|
});
|
|
|
|
for (var i = 0; i < blackops.length; ++i) {
|
|
DomElems.blackops[blackops[i].name] = createElement("div", {
|
|
class:"bladeburner-action", name:blackops[i].name
|
|
});
|
|
DomElems.actionsAndSkillsList.appendChild(DomElems.blackops[blackops[i].name]);
|
|
if (this.blackops[[blackops[i].name]] == null) {break;} //Can't be found in completed blackops
|
|
}
|
|
}
|
|
|
|
Bladeburner.prototype.createSkillsContent = function() {
|
|
if (DomElems.actionsAndSkillsList == null || DomElems.actionsAndSkillsDesc == null) {
|
|
throw new Error("Bladeburner.createSkillsContent called with either " +
|
|
"DomElems.actionsAndSkillsList or DomElems.actionsAndSkillsDesc = null");
|
|
}
|
|
|
|
//Display Current multipliers
|
|
DomElems.actionsAndSkillsDesc.innerHTML =
|
|
"You will gain one skill point every " + RanksPerSkillPoint + " ranks.<br><br>" +
|
|
"Note that when upgrading a skill, the benefit for that skill is additive. " +
|
|
"However, the effects of different skills with each other is multiplicative.<br><br>"
|
|
var multKeys = Object.keys(this.skillMultipliers);
|
|
for (var i = 0; i < multKeys.length; ++i) {
|
|
var mult = this.skillMultipliers[multKeys[i]];
|
|
if (mult && mult !== 1) {
|
|
mult = formatNumber(mult, 3);
|
|
switch(multKeys[i]) {
|
|
case "successChanceAll":
|
|
DomElems.actionsAndSkillsDesc.innerHTML += "Total Success Chance: x" + mult + "<br>";
|
|
break;
|
|
case "successChanceStealth":
|
|
DomElems.actionsAndSkillsDesc.innerHTML += "Stealth Success Chance: x" + mult + "<br>";
|
|
break;
|
|
case "successChanceKill":
|
|
DomElems.actionsAndSkillsDesc.innerHTML += "Retirement Success Chance: x" + mult + "<br>";
|
|
break;
|
|
case "successChanceContract":
|
|
DomElems.actionsAndSkillsDesc.innerHTML += "Contract Success Chance: x" + mult + "<br>";
|
|
break;
|
|
case "successChanceOperation":
|
|
DomElems.actionsAndSkillsDesc.innerHTML += "Operation Success Chance: x" + mult + "<br>";
|
|
break;
|
|
case "successChanceEstimate":
|
|
DomElems.actionsAndSkillsDesc.innerHTML += "Synthoid Data Estimate: x" + mult + "<br>";
|
|
break;
|
|
case "actionTime":
|
|
DomElems.actionsAndSkillsDesc.innerHTML += "Action Time: x" + mult + "<br>";
|
|
break;
|
|
case "effHack":
|
|
DomElems.actionsAndSkillsDesc.innerHTML += "Hacking Skill: x" + mult + "<br>";
|
|
break;
|
|
case "effStr":
|
|
DomElems.actionsAndSkillsDesc.innerHTML += "Strength: x" + mult + "<br>";
|
|
break;
|
|
case "effDef":
|
|
DomElems.actionsAndSkillsDesc.innerHTML += "Defense: x" + mult + "<br>";
|
|
break;
|
|
case "effDex":
|
|
DomElems.actionsAndSkillsDesc.innerHTML += "Dexterity: x" + mult + "<br>";
|
|
break;
|
|
case "effAgi":
|
|
DomElems.actionsAndSkillsDesc.innerHTML += "Agility: x" + mult + "<br>";
|
|
break;
|
|
case "effCha":
|
|
DomElems.actionsAndSkillsDesc.innerHTML += "Charisma: x" + mult + "<br>";
|
|
break;
|
|
case "effInt":
|
|
DomElems.actionsAndSkillsDesc.innerHTML += "Intelligence: x" + mult + "<br>";
|
|
break;
|
|
case "stamina":
|
|
DomElems.actionsAndSkillsDesc.innerHTML += "Stamina: x" + mult + "<br>";
|
|
break;
|
|
case "weaponAbility":
|
|
//DomElems.actionsAndSkillsDesc.innerHTML +=
|
|
break;
|
|
case "gunAbility":
|
|
//DomElems.actionsAndSkillsDesc.innerHTML
|
|
break;
|
|
default:
|
|
console.log("Warning: Unrecognized SkillMult Key: " + multKeys[i]);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
//Skill Points
|
|
DomElems.actionAndSkillsDiv.appendChild(createElement("p", {
|
|
innerHTML:"<br><strong>Skill Points: " + formatNumber(this.skillPoints, 0) + "</strong>"
|
|
}));
|
|
|
|
//UI Element for each skill
|
|
for (var skillName in Skills) {
|
|
if (Skills.hasOwnProperty(skillName)) {
|
|
DomElems.skills[skillName] = createElement("div", {
|
|
class:"bladeburner-action", name:skillName
|
|
});
|
|
DomElems.actionsAndSkillsList.appendChild(DomElems.skills[skillName]);
|
|
}
|
|
}
|
|
}
|
|
|
|
Bladeburner.prototype.updateContent = function() {
|
|
this.updateOverviewContent();
|
|
this.updateActionAndSkillsContent();
|
|
}
|
|
|
|
Bladeburner.prototype.updateOverviewContent = function() {
|
|
if (Engine.currentPage !== Engine.Page.Bladeburner) {return;}
|
|
DomElems.overviewRank.childNodes[0].nodeValue = "Rank: " + formatNumber(this.rank, 2);
|
|
DomElems.overviewStamina.innerText = "Stamina: " + formatNumber(this.stamina, 3) + " / " + formatNumber(this.maxStamina, 3);
|
|
DomElems.overviewGen1.innerHTML =
|
|
"Stamina Penalty: " + formatNumber((1-this.calculateStaminaPenalty())*100, 1) + "%<br><br>" +
|
|
"Team Size: " + formatNumber(this.teamSize, 0) + "<br>" +
|
|
"Team Members Lost: " + formatNumber(this.teamLost, 0) + "<br><br>" +
|
|
"Num Times Hospitalized: " + this.numHosp + "<br>" +
|
|
"Money Lost From Hospitalizations: " + numeral(this.moneyLost).format("$0.000a") + "<br><br>" +
|
|
"Current City: " + this.city + "<br>";
|
|
|
|
DomElems.overviewEstPop.childNodes[0].nodeValue = "Est. Synthoid Population: " + numeral(this.getCurrentCity().popEst).format("0.000a");
|
|
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.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) + "%";
|
|
DomElems.overviewAugAnalysisMult.innerText = "Aug. Field Analysis Mult: " + formatNumber(Player.bladeburner_analysis_mult*100, 1) + "%";
|
|
}
|
|
|
|
Bladeburner.prototype.updateActionAndSkillsContent = function() {
|
|
if (DomElems.currentTab == null) {DomElems.currentTab = "general";}
|
|
switch(DomElems.currentTab.toLowerCase()) {
|
|
case "general":
|
|
var actionElems = Object.keys(DomElems.generalActions);
|
|
for (var i = 0; i < actionElems.length; ++i) {
|
|
var actionElem = DomElems.generalActions[actionElems[i]];
|
|
var name = actionElem.name;
|
|
var actionObj = GeneralActions[name];
|
|
if (actionObj == null) {
|
|
throw new Error("Could not find Object " + name + " in Bladeburner.updateActionAndSkillsContent()");
|
|
}
|
|
if (this.action.type === ActionTypes[name]) {
|
|
actionElem.classList.add(ActiveActionCssClass);
|
|
} else {
|
|
actionElem.classList.remove(ActiveActionCssClass);
|
|
}
|
|
this.updateGeneralActionsUIElement(actionElem, actionObj);
|
|
}
|
|
break;
|
|
case "contracts":
|
|
var contractElems = Object.keys(DomElems.contracts);
|
|
for (var i = 0; i < contractElems.length; ++i) {
|
|
var contractElem = DomElems.contracts[contractElems[i]];
|
|
var name = contractElem.name;
|
|
if (this.action.type === ActionTypes["Contract"] && name === this.action.name) {
|
|
contractElem.classList.add(ActiveActionCssClass);
|
|
} else {
|
|
contractElem.classList.remove(ActiveActionCssClass);
|
|
}
|
|
var contract = this.contracts[name];
|
|
if (contract == null) {
|
|
throw new Error("Could not find Contract " + name + " in Bladeburner.updateActionAndSkillsContent()");
|
|
}
|
|
this.updateContractsUIElement(contractElem, contract);
|
|
}
|
|
break;
|
|
case "operations":
|
|
var operationElems = Object.keys(DomElems.operations);
|
|
for (var i = 0; i < operationElems.length; ++i) {
|
|
var operationElem = DomElems.operations[operationElems[i]];
|
|
var name = operationElem.name;
|
|
if (this.action.type === ActionTypes["Operation"] && name === this.action.name) {
|
|
operationElem.classList.add(ActiveActionCssClass);
|
|
} else {
|
|
operationElem.classList.remove(ActiveActionCssClass);
|
|
}
|
|
var operation = this.operations[name];
|
|
if (operation == null) {
|
|
throw new Error("Could not find Operation " + name + " in Bladeburner.updateActionAndSkillsContent()");
|
|
}
|
|
this.updateOperationsUIElement(operationElem, operation);
|
|
}
|
|
break;
|
|
case "blackops":
|
|
var blackopsElems = Object.keys(DomElems.blackops);
|
|
for (var i = 0; i < blackopsElems.length; ++i) {
|
|
var blackopElem = DomElems.blackops[blackopsElems[i]];
|
|
var name = blackopElem.name;
|
|
if (this.action.type === ActionTypes["BlackOperation"] && name === this.action.name) {
|
|
blackopElem.classList.add(ActiveActionCssClass);
|
|
} else {
|
|
blackopElem.classList.remove(ActiveActionCssClass);
|
|
}
|
|
var blackop = BlackOperations[name];
|
|
if (blackop == null) {
|
|
throw new Error("Could not find BlackOperation " + name + " in Bladeburner.updateActionAndSkillsContent()");
|
|
}
|
|
this.updateBlackOpsUIElement(blackopElem, blackop);
|
|
}
|
|
break;
|
|
case "skills":
|
|
var skillElems = Object.keys(DomElems.skills);
|
|
for (var i = 0; i < skillElems.length; ++i) {
|
|
var skillElem = DomElems.skills[skillElems[i]];
|
|
var name = skillElem.name;
|
|
var skill = Skills[name];
|
|
if (skill == null) {
|
|
throw new Error("Could not find Skill " + name + " in Bladeburner.updateActionAndSkillsContent()");
|
|
}
|
|
this.updateSkillsUIElement(skillElem, skill);
|
|
}
|
|
break;
|
|
default:
|
|
throw new Error("Invalid value for DomElems.currentTab in Bladeburner.createActionAndSkillsContent");
|
|
}
|
|
}
|
|
|
|
Bladeburner.prototype.updateGeneralActionsUIElement = function(el, action) {
|
|
removeChildrenFromElement(el);
|
|
var isActive = el.classList.contains(ActiveActionCssClass);
|
|
|
|
el.appendChild(createElement("h2", { //Header
|
|
innerText:isActive ? action.name + " (IN PROGRESS - " +
|
|
formatNumber(this.actionTimeCurrent, 0) + " / " +
|
|
formatNumber(this.actionTimeToComplete, 0) + ")"
|
|
: action.name,
|
|
display:"inline-block",
|
|
}));
|
|
|
|
if (isActive) { //Progress bar if its active
|
|
var progress = this.actionTimeCurrent / this.actionTimeToComplete;
|
|
el.appendChild(createElement("p", {
|
|
display:"block",
|
|
innerText:createProgressBarText({progress:progress})
|
|
}));
|
|
} else {
|
|
//Start button
|
|
el.appendChild(createElement("a", {
|
|
innerText:"Start", class: "a-link-button",
|
|
margin:"3px", padding:"3px",
|
|
clickListener:()=>{
|
|
this.action.type = ActionTypes[action.name];
|
|
this.action.name = action.name;
|
|
this.startAction(this.action);
|
|
this.updateActionAndSkillsContent();
|
|
return false;
|
|
}
|
|
}));
|
|
}
|
|
|
|
appendLineBreaks(el, 2);
|
|
el.appendChild(createElement("pre", { //Info
|
|
innerHTML:action.desc, display:"inline-block"
|
|
}));
|
|
|
|
|
|
}
|
|
|
|
Bladeburner.prototype.updateContractsUIElement = function(el, action) {
|
|
removeChildrenFromElement(el);
|
|
var isActive = el.classList.contains(ActiveActionCssClass);
|
|
var estimatedSuccessChance = action.getSuccessChance(this, {est:true});
|
|
|
|
el.appendChild(createElement("h2", { //Header
|
|
innerText:isActive ? action.name + " (IN PROGRESS - " +
|
|
formatNumber(this.actionTimeCurrent, 0) + " / " +
|
|
formatNumber(this.actionTimeToComplete, 0) + ")"
|
|
: action.name,
|
|
display:"inline-block"
|
|
}));
|
|
|
|
if (isActive) { //Progress bar if its active
|
|
var progress = this.actionTimeCurrent / this.actionTimeToComplete;
|
|
el.appendChild(createElement("p", {
|
|
display:"block",
|
|
innerText:createProgressBarText({progress:progress})
|
|
}));
|
|
} else { //Start button
|
|
el.appendChild(createElement("a", {
|
|
innerText:"Start", class: "a-link-button",
|
|
padding:"3px", margin:"3px",
|
|
clickListener:()=>{
|
|
this.action.type = ActionTypes.Contract;
|
|
this.action.name = action.name;
|
|
this.startAction(this.action);
|
|
this.updateActionAndSkillsContent();
|
|
return false;
|
|
}
|
|
}));
|
|
}
|
|
|
|
//Level and buttons to change level
|
|
var maxLevel = (action.level >= action.maxLevel);
|
|
appendLineBreaks(el, 2);
|
|
el.appendChild(createElement("pre", {
|
|
display:"inline-block",
|
|
innerText:"Level: " + action.level + " / " + action.maxLevel
|
|
}));
|
|
el.appendChild(createElement("a", {
|
|
class: maxLevel ? "a-link-button-inactive" : "a-link-button", innerHTML:"↑",
|
|
padding:"2px", margin:"2px",
|
|
tooltip: isActive ? "WARNING: changing the level will restart the contract" : "",
|
|
display:"inline",
|
|
clickListener:()=>{
|
|
++action.level;
|
|
if (isActive) {this.startAction(this.action);} //Restart Action
|
|
this.updateContractsUIElement(el, action);
|
|
return false;
|
|
}
|
|
}));
|
|
el.appendChild(createElement("a", {
|
|
class: (action.level <= 1) ? "a-link-button-inactive" : "a-link-button", innerHTML:"↓",
|
|
padding:"2px", margin:"2px",
|
|
tooltip: isActive ? "WARNING: changing the level will restart the contract" : "",
|
|
display:"inline",
|
|
clickListener:()=>{
|
|
--action.level;
|
|
if (isActive) {this.startAction(this.action);} //Restart Action
|
|
this.updateContractsUIElement(el, action);
|
|
return false;
|
|
}
|
|
}));
|
|
|
|
var actionTime = action.getActionTime(this);
|
|
appendLineBreaks(el, 2);
|
|
el.appendChild(createElement("pre", { //Info
|
|
display:"inline-block",
|
|
innerHTML:action.desc + "\n\n" +
|
|
"Estimated success chance: " + formatNumber(estimatedSuccessChance*100, 1) + "%\n" +
|
|
"Time Required (s): " + formatNumber(actionTime, 0) + "\n" +
|
|
"Contracts remaining: " + Math.floor(action.count) + "\n" +
|
|
"Successes: " + action.successes + "\n" +
|
|
"Failures: " + action.failures,
|
|
}));
|
|
|
|
//Autolevel Checkbox
|
|
el.appendChild(createElement("br"));
|
|
var autolevelCheckboxId = "bladeburner-" + action.name + "-autolevel-checkbox";
|
|
el.appendChild(createElement("label", {
|
|
for:autolevelCheckboxId, innerText:"Autolevel",color:"white",
|
|
tooltip:"Automatically increase contract level when possible"
|
|
}));
|
|
var autolevelCheckbox = createElement("input", {
|
|
type:"checkbox", id:autolevelCheckboxId, margin:"4px",
|
|
checked:action.autoLevel,
|
|
changeListener:()=>{
|
|
action.autoLevel = autolevelCheckbox.checked;
|
|
}
|
|
});
|
|
el.appendChild(autolevelCheckbox);
|
|
}
|
|
|
|
Bladeburner.prototype.updateOperationsUIElement = function(el, action) {
|
|
removeChildrenFromElement(el);
|
|
var isActive = el.classList.contains(ActiveActionCssClass);
|
|
var estimatedSuccessChance = action.getSuccessChance(this, {est:true});
|
|
el.appendChild(createElement("h2", { //Header
|
|
innerText:isActive ? action.name + " (IN PROGRESS - " +
|
|
formatNumber(this.actionTimeCurrent, 0) + " / " +
|
|
formatNumber(this.actionTimeToComplete, 0) + ")"
|
|
: action.name,
|
|
display:"inline-block"
|
|
}));
|
|
|
|
if (isActive) { //Progress bar if its active
|
|
var progress = this.actionTimeCurrent / this.actionTimeToComplete;
|
|
el.appendChild(createElement("p", {
|
|
display:"block",
|
|
innerText:createProgressBarText({progress:progress})
|
|
}));
|
|
} else { //Start button and set Team Size button
|
|
el.appendChild(createElement("a", {
|
|
innerText:"Start", class: "a-link-button",
|
|
margin:"3px", padding:"3px",
|
|
clickListener:()=>{
|
|
this.action.type = ActionTypes.Operation;
|
|
this.action.name = action.name;
|
|
this.startAction(this.action);
|
|
this.updateActionAndSkillsContent();
|
|
return false;
|
|
}
|
|
}));
|
|
el.appendChild(createElement("a", {
|
|
innerText:"Set Team Size (Curr Size: " + formatNumber(action.teamCount, 0) + ")", class:"a-link-button",
|
|
margin:"3px", padding:"3px",
|
|
clickListener:()=>{
|
|
var popupId = "bladeburner-operation-set-team-size-popup";
|
|
var txt = createElement("p", {
|
|
innerText:"Enter the amount of team members you would like to take on these " +
|
|
"operations. If you do not have the specified number of team members, " +
|
|
"then as many as possible will be used. Note that team members may " +
|
|
"be lost during operations."
|
|
|
|
});
|
|
var input = createElement("input", {
|
|
type:"number", placeholder: "Team Members"
|
|
});
|
|
var setBtn = createElement("a", {
|
|
innerText:"Confirm", class:"a-link-button",
|
|
clickListener:()=>{
|
|
var num = Math.round(parseFloat(input.value));
|
|
if (isNaN(num)) {
|
|
dialogBoxCreate("Invalid value entered for number of Team Members (must be numeric)")
|
|
} else {
|
|
action.teamCount = num;
|
|
this.updateOperationsUIElement(el, action);
|
|
}
|
|
removeElementById(popupId);
|
|
return false;
|
|
}
|
|
});
|
|
var cancelBtn = createElement("a", {
|
|
innerText:"Cancel", class:"a-link-button",
|
|
clickListener:()=>{
|
|
removeElementById(popupId);
|
|
return false;
|
|
}
|
|
});
|
|
createPopup(popupId, [txt, input, setBtn, cancelBtn]);
|
|
}
|
|
}));
|
|
}
|
|
|
|
//Level and buttons to change level
|
|
var maxLevel = (action.level >= action.maxLevel);
|
|
appendLineBreaks(el, 2);
|
|
el.appendChild(createElement("pre", {
|
|
display:"inline-block",
|
|
innerText:"Level: " + action.level + " / " + action.maxLevel
|
|
}));
|
|
el.appendChild(createElement("a", {
|
|
class: maxLevel ? "a-link-button-inactive" : "a-link-button", innerHTML:"↑",
|
|
padding:"2px", margin:"2px",
|
|
tooltip: isActive ? "WARNING: changing the level will restart the Operation" : "",
|
|
display:"inline",
|
|
clickListener:()=>{
|
|
++action.level;
|
|
if (isActive) {this.startAction(this.action);} //Restart Action
|
|
this.updateOperationsUIElement(el, action);
|
|
return false;
|
|
}
|
|
}));
|
|
el.appendChild(createElement("a", {
|
|
class: (action.level <= 1) ? "a-link-button-inactive" : "a-link-button", innerHTML:"↓",
|
|
padding:"2px", margin:"2px",
|
|
tooltip: isActive ? "WARNING: changing the level will restart the Operation" : "",
|
|
display:"inline",
|
|
clickListener:()=>{
|
|
--action.level;
|
|
if (isActive) {this.startAction(this.action);} //Restart Action
|
|
this.updateOperationsUIElement(el, action);
|
|
return false;
|
|
}
|
|
}));
|
|
|
|
//General Info
|
|
var difficulty = action.getDifficulty();
|
|
var actionTime = action.getActionTime(this);
|
|
appendLineBreaks(el, 2);
|
|
el.appendChild(createElement("pre", {
|
|
display:"inline-block",
|
|
innerHTML:action.desc + "\n\n" +
|
|
"Estimated success chance: " + formatNumber(estimatedSuccessChance*100, 1) + "%\n" +
|
|
"Time Required(s): " + formatNumber(actionTime, 1) + "\n" +
|
|
"Operations remaining: " + Math.floor(action.count) + "\n" +
|
|
"Successes: " + action.successes + "\n" +
|
|
"Failures: " + action.failures,
|
|
}));
|
|
|
|
//Autolevel Checkbox
|
|
el.appendChild(createElement("br"));
|
|
var autolevelCheckboxId = "bladeburner-" + action.name + "-autolevel-checkbox";
|
|
el.appendChild(createElement("label", {
|
|
for:autolevelCheckboxId, innerText:"Autolevel",color:"white",
|
|
tooltip:"Automatically increase operation level when possible"
|
|
}));
|
|
var autolevelCheckbox = createElement("input", {
|
|
type:"checkbox", id:autolevelCheckboxId, margin:"4px",
|
|
checked:action.autoLevel,
|
|
changeListener:()=>{
|
|
action.autoLevel = autolevelCheckbox.checked;
|
|
}
|
|
});
|
|
el.appendChild(autolevelCheckbox);
|
|
}
|
|
|
|
Bladeburner.prototype.updateBlackOpsUIElement = function(el, action) {
|
|
removeChildrenFromElement(el);
|
|
var isActive = el.classList.contains(ActiveActionCssClass);
|
|
var isCompleted = (this.blackops[action.name] != null);
|
|
var estimatedSuccessChance = action.getSuccessChance(this, {est:true});
|
|
var difficulty = action.getDifficulty();
|
|
var actionTime = action.getActionTime(this);
|
|
var hasReqdRank = this.rank >= action.reqdRank;
|
|
|
|
//UI for Completed Black Op
|
|
if (isCompleted) {
|
|
el.appendChild(createElement("h2", {
|
|
innerText:action.name + " (COMPLETED)", display:"block",
|
|
}));
|
|
return;
|
|
}
|
|
|
|
el.appendChild(createElement("h2", { //Header
|
|
innerText:isActive ? action.name + " (IN PROGRESS - " +
|
|
formatNumber(this.actionTimeCurrent, 0) + " / " +
|
|
formatNumber(this.actionTimeToComplete, 0) + ")"
|
|
: action.name,
|
|
display:"inline-block",
|
|
}));
|
|
|
|
if (isActive) { //Progress bar if its active
|
|
var progress = this.actionTimeCurrent / this.actionTimeToComplete;
|
|
el.appendChild(createElement("p", {
|
|
display:"block",
|
|
innerText:createProgressBarText({progress:progress})
|
|
}));
|
|
} else {
|
|
el.appendChild(createElement("a", { //Start button
|
|
innerText:"Start", margin:"3px", padding:"3px",
|
|
class:hasReqdRank ? "a-link-button" : "a-link-button-inactive",
|
|
clickListener:()=>{
|
|
this.action.type = ActionTypes.BlackOperation;
|
|
this.action.name = action.name;
|
|
this.startAction(this.action);
|
|
this.updateActionAndSkillsContent();
|
|
return false;
|
|
}
|
|
}));
|
|
el.appendChild(createElement("a", { //Set Team Size Button
|
|
innerText:"Set Team Size (Curr Size: " + formatNumber(action.teamCount, 0) + ")", class:"a-link-button",
|
|
margin:"3px", padding:"3px",
|
|
clickListener:()=>{
|
|
var popupId = "bladeburner-operation-set-team-size-popup";
|
|
var txt = createElement("p", {
|
|
innerText:"Enter the amount of team members you would like to take on this " +
|
|
"BlackOp. If you do not have the specified number of team members, " +
|
|
"then as many as possible will be used. Note that team members may " +
|
|
"be lost during operations."
|
|
|
|
});
|
|
var input = createElement("input", {
|
|
type:"number", placeholder: "Team Members"
|
|
});
|
|
var setBtn = createElement("a", {
|
|
innerText:"Confirm", class:"a-link-button",
|
|
clickListener:()=>{
|
|
var num = Math.round(parseFloat(input.value));
|
|
if (isNaN(num)) {
|
|
dialogBoxCreate("Invalid value entered for number of Team Members (must be numeric)")
|
|
} else {
|
|
action.teamCount = num;
|
|
this.updateBlackOpsUIElement(el, action);
|
|
}
|
|
removeElementById(popupId);
|
|
return false;
|
|
}
|
|
});
|
|
var cancelBtn = createElement("a", {
|
|
innerText:"Cancel", class:"a-link-button",
|
|
clickListener:()=>{
|
|
removeElementById(popupId);
|
|
return false;
|
|
}
|
|
});
|
|
createPopup(popupId, [txt, input, setBtn, cancelBtn]);
|
|
}
|
|
}));
|
|
}
|
|
|
|
//Info
|
|
appendLineBreaks(el, 2);
|
|
el.appendChild(createElement("p", {
|
|
display:"inline-block",
|
|
innerHTML:"<br>" + action.desc + "<br><br>",
|
|
}));
|
|
el.appendChild(createElement("p", {
|
|
display:"block", color:hasReqdRank ? "white" : "red",
|
|
innerHTML:"Required Rank: " + formatNumber(action.reqdRank, 0) + "<br>"
|
|
}));
|
|
el.appendChild(createElement("p", {
|
|
display:"inline-block",
|
|
innerHTML:"Estimated Success Chance: " + formatNumber(estimatedSuccessChance*100, 1) + "%\n" +
|
|
"Time Required(s): " + formatNumber(actionTime, 1),
|
|
}))
|
|
}
|
|
|
|
Bladeburner.prototype.updateSkillsUIElement = function(el, skill) {
|
|
removeChildrenFromElement(el);
|
|
var skillName = skill.name;
|
|
var currentLevel = 0;
|
|
if (this.skills[skillName] && !isNaN(this.skills[skillName])) {
|
|
currentLevel = this.skills[skillName];
|
|
}
|
|
var pointCost = skill.baseCost + (currentLevel * skill.costInc);
|
|
|
|
el.appendChild(createElement("h2", { //Header
|
|
innerText:skill.name + " (Lvl " + currentLevel + ")", display:"inline-block"
|
|
}));
|
|
|
|
var canLevel = this.skillPoints >= pointCost;
|
|
var maxLvl = skill.maxLvl ? currentLevel >= skill.maxLvl : false;
|
|
el.appendChild(createElement("a", { //Level up button
|
|
innerText:"Level", display:"inline-block",
|
|
class: canLevel && !maxLvl ? "a-link-button" : "a-link-button-inactive",
|
|
margin:"3px", padding:"3px",
|
|
clickListener:()=>{
|
|
this.skillPoints -= pointCost;
|
|
this.upgradeSkill(skill);
|
|
this.createActionAndSkillsContent();
|
|
return false;
|
|
}
|
|
}));
|
|
appendLineBreaks(el, 2);
|
|
if (maxLvl) {
|
|
el.appendChild(createElement("p", {
|
|
color:"red", display:"block",
|
|
innerText:"MAX LEVEL"
|
|
}));
|
|
} else {
|
|
el.appendChild(createElement("p", {
|
|
display:"block",
|
|
innerText:"Skill Points required: " + formatNumber(pointCost, 0),
|
|
}));
|
|
}
|
|
el.appendChild(createElement("p", { //Info/Description
|
|
innerHTML:skill.desc, display:"inline-block",
|
|
}));
|
|
}
|
|
|
|
//Bladeburner Console Window
|
|
Bladeburner.prototype.postToConsole = function(input) {
|
|
if (input == null || DomElems.consoleDiv == null) {return;}
|
|
$("#bladeubrner-console-input-row").before('<tr><td class="bladeburner-console-line" style="color: var(--my-font-color); white-space:pre-wrap;">' + input + '</td></tr>');
|
|
if (DomElems.consoleTable.childNodes.length > 200) {
|
|
DomElems.consoleTable.removeChild(DomElems.consoleTable.firstChild);
|
|
}
|
|
this.updateConsoleScroll();
|
|
}
|
|
|
|
Bladeburner.prototype.updateConsoleScroll = function() {
|
|
DomElems.consoleDiv.scrollTop = DomElems.consoleDiv.scrollHeight;
|
|
}
|
|
|
|
Bladeburner.prototype.resetConsoleInput = function() {
|
|
DomElems.consoleInput.value = "";
|
|
}
|
|
|
|
Bladeburner.prototype.clearConsole = function() {
|
|
while (DomElems.consoleTable.childNodes.length > 1) {
|
|
DomElems.consoleTable.removeChild(DomElems.consoleTable.firstChild);
|
|
}
|
|
}
|
|
|
|
Bladeburner.prototype.log = function(input) {
|
|
//Adds a timestamp and then just calls postToConsole
|
|
var d = new Date();
|
|
var timestamp = d.getMonth() + "/" + d.getDay() + " " + d.getHours() + ":" + d.getMinutes();
|
|
this.postToConsole("[" + timestamp + "] " + input);
|
|
}
|
|
|
|
//Handles a potential series of commands (comm1; comm2; comm3;)
|
|
Bladeburner.prototype.executeConsoleCommands = function(commands) {
|
|
try {
|
|
//Console History
|
|
if (consoleHistory[consoleHistory.length-1] != commands) {
|
|
consoleHistory.push(commands);
|
|
if (consoleHistory.length > 50) {
|
|
consoleHistory.splice(0, 1);
|
|
}
|
|
}
|
|
consoleHistoryIndex = consoleHistory.length;
|
|
|
|
var arrayOfCommands = commands.split(";");
|
|
for (var i = 0; i < arrayOfCommands.length; ++i) {
|
|
this.executeConsoleCommand(arrayOfCommands[i]);
|
|
}
|
|
} catch(e) {
|
|
exceptionAlert(e);
|
|
}
|
|
}
|
|
|
|
//A single command
|
|
Bladeburner.prototype.executeConsoleCommand = function(command) {
|
|
command = command.trim();
|
|
command = command.replace(/\s\s+/g, ' '); //Replace all whitespace w/ a single space
|
|
|
|
var args = this.parseCommandArguments(command);
|
|
if (args.length <= 0) {return;} //Log an error?
|
|
|
|
switch(args[0].toLowerCase()) {
|
|
case "automate":
|
|
this.executeAutomateConsoleCommand(args);
|
|
break;
|
|
case "clear":
|
|
case "cls":
|
|
this.clearConsole();
|
|
break;
|
|
case "help":
|
|
this.executeHelpConsoleCommand(args);
|
|
break;
|
|
case "log":
|
|
this.executeLogConsoleCommand(args);
|
|
break;
|
|
case "skill":
|
|
this.executeSkillConsoleCommand(args);
|
|
break;
|
|
case "start":
|
|
this.executeStartConsoleCommand(args);
|
|
break;
|
|
case "stop":
|
|
this.resetAction();
|
|
break;
|
|
default:
|
|
this.postToConsole("Invalid console command");
|
|
break;
|
|
}
|
|
}
|
|
|
|
Bladeburner.prototype.parseCommandArguments = function(command) {
|
|
//Returns an array with command and its arguments in each index.
|
|
//e.g. skill "blade's intuition" foo returns [skill, blade's intuition, foo]
|
|
//The input to this fn will be trimmed and will have all whitespace replaced w/ a single space
|
|
var args = [];
|
|
var start = 0, i = 0;
|
|
while (i < command.length) {
|
|
var c = command.charAt(i);
|
|
if (c === '"') {
|
|
var endQuote = command.indexOf('"', i+1);
|
|
if (endQuote !== -1 && (endQuote === command.length-1 || command.charAt(endQuote+1) === " ")) {
|
|
args.push(command.substr(i+1, (endQuote - i - 1)));
|
|
if (endQuote === command.length-1) {
|
|
start = i = endQuote+1;
|
|
} else {
|
|
start = i = endQuote+2; //Skip the space
|
|
}
|
|
continue;
|
|
}
|
|
} else if (c === " ") {
|
|
args.push(command.substr(start, i-start));
|
|
start = i+1;
|
|
}
|
|
++i;
|
|
}
|
|
if (start !== i) {args.push(command.substr(start, i-start));}
|
|
console.log("Bladeburner.parseCommandArguments returned: " + args);
|
|
return args;
|
|
}
|
|
|
|
Bladeburner.prototype.executeAutomateConsoleCommand = function(args) {
|
|
if (args.length !== 2 && args.length !== 4) {
|
|
this.postToConsole("Invalid use of 'automate' command: automate [var] [val] [hi/low]. Use 'help automate' for more info");
|
|
return;
|
|
}
|
|
|
|
//Enable/Disable
|
|
if (args.length === 2) {
|
|
var flag = args[1];
|
|
if (flag.toLowerCase() === "status") {
|
|
this.postToConsole("Automation: " + (this.automateEnabled ? "enabled" : "disabled"));
|
|
if (this.automateEnabled) {
|
|
this.postToConsole("When your stamina drops to " + formatNumber(this.automateThreshLow, 0) +
|
|
", you will automatically switch to " + this.automateActionLow.name +
|
|
". When your stamina recovers to " +
|
|
formatNumber(this.automateThreshHigh, 0) + ", you will automatically " +
|
|
"switch to " + this.automateActionHigh.name + ".");
|
|
}
|
|
|
|
} else if (flag.toLowerCase().includes("en")) {
|
|
if (!(this.automateActionLow instanceof ActionIdentifier) ||
|
|
!(this.automateActionHigh instanceof ActionIdentifier)) {
|
|
return this.log("Failed to enable automation. Actions were not set");
|
|
}
|
|
this.automateEnabled = true;
|
|
this.log("Bladeburner automation enabled");
|
|
} else if (flag.toLowerCase().includes("d")) {
|
|
this.automateEnabled = false;
|
|
this.log("Bladeburner automation disabled");
|
|
} else {
|
|
this.log("Invalid argument for 'automate' console command: " + args[1]);
|
|
}
|
|
return;
|
|
}
|
|
|
|
//Set variables
|
|
if (args.length === 4) {
|
|
var variable = args[1], val = args[2];
|
|
|
|
var highLow = false; //True for high, false for low
|
|
if (args[3].toLowerCase().includes("hi")) {highLow = true;}
|
|
|
|
switch (variable) {
|
|
case "general":
|
|
case "gen":
|
|
if (GeneralActions[val] != null) {
|
|
var action = new ActionIdentifier({
|
|
type:ActionTypes[val], name:val
|
|
});
|
|
if (highLow) {
|
|
this.automateActionHigh = action;
|
|
} else {
|
|
this.automateActionLow = action;
|
|
}
|
|
this.log("Automate (" + (highLow ? "HIGH" : "LOW") + ") action set to " + val);
|
|
} else {
|
|
this.postToConsole("Invalid action name specified: " + val);
|
|
}
|
|
break;
|
|
case "contract":
|
|
case "contracts":
|
|
if (this.contracts[val] != null) {
|
|
var action = new ActionIdentifier({
|
|
type:ActionTypes.Contract, name:val
|
|
});
|
|
if (highLow) {
|
|
this.automateActionHigh = action;
|
|
} else {
|
|
this.automateActionLow = action;
|
|
}
|
|
this.log("Automate (" + (highLow ? "HIGH" : "LOW") + ") action set to " + val);
|
|
} else {
|
|
this.postToConsole("Invalid contract name specified: " + val);
|
|
}
|
|
break;
|
|
case "ops":
|
|
case "op":
|
|
case "operations":
|
|
case "operation":
|
|
if (this.operations[val] != null) {
|
|
var action = new ActionIdentifier({
|
|
type:ActionTypes.Operation, name:val
|
|
});
|
|
if (highLow) {
|
|
this.automateActionHigh = action;
|
|
} else {
|
|
this.automateActionLow = action;
|
|
}
|
|
this.log("Automate (" + (highLow ? "HIGH" : "LOW") + ") action set to " + val);
|
|
} else {
|
|
this.postToConsole("Invalid Operation name specified: " + val);
|
|
}
|
|
break;
|
|
case "stamina":
|
|
if (isNaN(val)) {
|
|
this.postToConsole("Invalid value specified for stamina threshold (must be numeric): " + val);
|
|
} else {
|
|
if (highLow) {
|
|
this.automateThreshHigh = Number(val);
|
|
} else {
|
|
this.automateThreshLow = Number(val);
|
|
}
|
|
this.log("Automate (" + (highLow ? "HIGH" : "LOW") + ") stamina threshold set to " + val);
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
Bladeburner.prototype.executeHelpConsoleCommand = function(args) {
|
|
if (args.length === 1) {
|
|
this.postToConsole(consoleHelpText.helpList);
|
|
} else {
|
|
for (var i = 1; i < args.length; ++i) {
|
|
var commandText = consoleHelpText[args[i]];
|
|
if (commandText != null) {
|
|
this.postToConsole(commandText);
|
|
this.postToConsole("<br>");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Bladeburner.prototype.executeLogConsoleCommand = function(args) {
|
|
if (args.length < 3) {
|
|
this.postToConsole("Invalid usage of log command: log [enable/disable] [action/event]");
|
|
this.postToConsole("Use 'help log' for more details and examples");
|
|
return;
|
|
}
|
|
|
|
var flag = true;
|
|
if (args[1].toLowerCase().includes("d")) {flag = false;} //d for disable
|
|
|
|
switch (args[2].toLowerCase()) {
|
|
case "general":
|
|
case "gen":
|
|
this.logging.general = flag;
|
|
this.log("Logging " + (flag ? "enabled" : "disabled") + " for general actions");
|
|
break;
|
|
case "contract":
|
|
case "contracts":
|
|
this.logging.contracts = flag;
|
|
this.log("Logging " + (flag ? "enabled" : "disabled") + " for Contracts");
|
|
break;
|
|
case "ops":
|
|
case "op":
|
|
case "operations":
|
|
case "operation":
|
|
this.logging.ops = flag;
|
|
this.log("Logging " + (flag ? "enabled" : "disabled") + " for Operations");
|
|
break;
|
|
case "blackops":
|
|
case "blackop":
|
|
case "black operations":
|
|
case "black operation":
|
|
this.logging.blackops = flag;
|
|
this.log("Logging " + (flag ? "enabled" : "disabled") + " for BlackOps");
|
|
break;
|
|
case "event":
|
|
case "events":
|
|
this.logging.events = flag;
|
|
this.log("Logging " + (flag ? "enabled" : "disabled") + " for events");
|
|
break;
|
|
case "all":
|
|
this.logging.general = flag;
|
|
this.logging.contracts = flag;
|
|
this.logging.ops = flag;
|
|
this.logging.blackops = flag;
|
|
this.logging.events = flag;
|
|
this.log("Logging " + (flag ? "enabled" : "disabled") + " for everything");
|
|
break;
|
|
default:
|
|
this.postToConsole("Invalid action/event type specified: " + args[2]);
|
|
this.postToConsole("Examples of valid action/event identifiers are: [general, contracts, ops, blackops, events]");
|
|
break;
|
|
}
|
|
}
|
|
|
|
Bladeburner.prototype.executeSkillConsoleCommand = function(args) {
|
|
switch (args.length) {
|
|
case 1:
|
|
//Display Skill Help Command
|
|
this.postToConsole("Invalid usage of 'skill' console command: skill [action] [name]");
|
|
this.postToConsole("Use 'help skill' for more info");
|
|
break;
|
|
case 2:
|
|
if (args[1].toLowerCase() === "list") {
|
|
//List all skills and their level
|
|
this.postToConsole("Skills: ");
|
|
var skillNames = Object.keys(Skills);
|
|
for(var i = 0; i < skillNames.length; ++i) {
|
|
var skill = Skills[skillNames[i]];
|
|
var level = 0;
|
|
if (this.skills[skill.name] != null) {level = this.skills[skill.name];}
|
|
this.postToConsole(skill.name + ": Level " + formatNumber(level, 0));
|
|
}
|
|
this.postToConsole(" ");
|
|
this.postToConsole("Effects: ");
|
|
var multKeys = Object.keys(this.skillMultipliers);
|
|
for (var i = 0; i < multKeys.length; ++i) {
|
|
var mult = this.skillMultipliers[multKeys[i]];
|
|
if (mult && mult !== 1) {
|
|
mult = formatNumber(mult, 3);
|
|
switch(multKeys[i]) {
|
|
case "successChanceAll":
|
|
this.postToConsole("Total Success Chance: x" + mult);
|
|
break;
|
|
case "successChanceStealth":
|
|
this.postToConsole("Stealth Success Chance: x" + mult);
|
|
break;
|
|
case "successChanceKill":
|
|
this.postToConsole("Retirement Success Chance: x" + mult);
|
|
break;
|
|
case "successChanceContract":
|
|
this.postToConsole("Contract Success Chance: x" + mult);
|
|
break;
|
|
case "successChanceOperation":
|
|
this.postToConsole("Operation Success Chance: x" + mult);
|
|
break;
|
|
case "successChanceEstimate":
|
|
this.postToConsole("Synthoid Data Estimate: x" + mult);
|
|
break;
|
|
case "actionTime":
|
|
this.postToConsole("Action Time: x" + mult);
|
|
break;
|
|
case "effHack":
|
|
this.postToConsole("Hacking Skill: x" + mult);
|
|
break;
|
|
case "effStr":
|
|
this.postToConsole("Strength: x" + mult);
|
|
break;
|
|
case "effDef":
|
|
this.postToConsole("Defense: x" + mult);
|
|
break;
|
|
case "effDex":
|
|
this.postToConsole("Dexterity: x" + mult);
|
|
break;
|
|
case "effAgi":
|
|
this.postToConsole("Agility: x" + mult);
|
|
break;
|
|
case "effCha":
|
|
this.postToConsole("Charisma: x" + mult);
|
|
break;
|
|
case "effInt":
|
|
this.postToConsole("Intelligence: x" + mult);
|
|
break;
|
|
case "stamina":
|
|
this.postToConsole("Stamina: x" + mult);
|
|
break;
|
|
case "weaponAbility":
|
|
//DomElems.actionsAndSkillsDesc.innerHTML +=
|
|
break;
|
|
case "gunAbility":
|
|
//DomElems.actionsAndSkillsDesc.innerHTML
|
|
break;
|
|
default:
|
|
console.log("Warning: Unrecognized SkillMult Key: " + multKeys[i]);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
this.postToConsole("Invalid usage of 'skill' console command: skill [action] [name]");
|
|
this.postToConsole("Use 'help skill' for more info");
|
|
}
|
|
break;
|
|
case 3:
|
|
var skillName = args[2];
|
|
var skill = Skills[skillName];
|
|
if (skill == null || !(skill instanceof Skill)) {
|
|
return this.postToConsole("Invalid skill name (Note that this is case-sensitive): " + skillName);
|
|
}
|
|
if (args[1].toLowerCase() === "list") {
|
|
this.postToConsole(skill.name + ": Level " + formatNumber(this.skills[skill.name]), 0);
|
|
} else if (args[1].toLowerCase() === "level") {
|
|
var currentLevel = 0;
|
|
if (this.skills[skillName] && !isNaN(this.skills[skillName])) {
|
|
currentLevel = this.skills[skillName];
|
|
}
|
|
var pointCost = skill.baseCost + (currentLevel * skill.costInc);
|
|
if (this.skillPoints >= pointCost) {
|
|
this.skillPoints -= pointCost;
|
|
this.upgradeSkill(skill);
|
|
this.log(skill.name + " upgraded to Level " + this.skills[skillName]);
|
|
this.createActionAndSkillsContent();
|
|
} else {
|
|
this.postToConsole("You do not have enough Skill Points to upgrade this. You need " + formatNumber(pointCost, 0));
|
|
}
|
|
|
|
} else {
|
|
this.postToConsole("Invalid usage of 'skill' console command: skill [action] [name]");
|
|
this.postToConsole("Use 'help skill' for more info");
|
|
}
|
|
break;
|
|
default:
|
|
this.postToConsole("Invalid usage of 'skill' console command: skill [action] [name]");
|
|
this.postToConsole("Use 'help skill' for more info");
|
|
break;
|
|
}
|
|
}
|
|
|
|
Bladeburner.prototype.executeStartConsoleCommand = function(args) {
|
|
if (args.length !== 3) {
|
|
this.postToConsole("Invalid usage of 'start' console command: start [type] [name]");
|
|
this.postToConsole("Use 'help start' for more info");
|
|
return;
|
|
}
|
|
var name = args[2];
|
|
switch (args[1].toLowerCase()) {
|
|
case "general":
|
|
case "gen":
|
|
if (GeneralActions[name] != null) {
|
|
this.action.type = ActionTypes[name];
|
|
this.action.name = name;
|
|
this.startAction(this.action);
|
|
this.updateActionAndSkillsContent();
|
|
} else {
|
|
this.postToConsole("Invalid action name specified: " + args[2]);
|
|
}
|
|
break;
|
|
case "contract":
|
|
case "contracts":
|
|
if (this.contracts[name] != null) {
|
|
this.action.type = ActionTypes.Contract;
|
|
this.action.name = name;
|
|
this.startAction(this.action);
|
|
this.updateActionAndSkillsContent();
|
|
} else {
|
|
this.postToConsole("Invalid contract name specified: " + args[2]);
|
|
}
|
|
break;
|
|
case "ops":
|
|
case "op":
|
|
case "operations":
|
|
case "operation":
|
|
if (this.operations[name] != null) {
|
|
this.action.type = ActionTypes.Operation;
|
|
this.action.name = name;
|
|
this.startAction(this.action);
|
|
this.updateActionAndSkillsContent();
|
|
} else {
|
|
this.postToConsole("Invalid Operation name specified: " + args[2]);
|
|
}
|
|
break;
|
|
case "blackops":
|
|
case "blackop":
|
|
case "black operations":
|
|
case "black operation":
|
|
if (BlackOperations[name] != null) {
|
|
this.action.type = ActionTypes.BlackOperation;
|
|
this.action.name = name;
|
|
this.startAction(this.action);
|
|
this.updateActionAndSkillsContent();
|
|
} else {
|
|
this.postToConsole("Invalid BlackOp name specified: " + args[2]);
|
|
}
|
|
break;
|
|
default:
|
|
this.postToConsole("Invalid action/event type specified: " + args[1]);
|
|
this.postToConsole("Examples of valid action/event identifiers are: [general, contract, op, blackop]");
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
Bladeburner.prototype.toJSON = function() {
|
|
return Generic_toJSON("Bladeburner", this);
|
|
}
|
|
Bladeburner.fromJSON = function(value) {
|
|
return Generic_fromJSON(Bladeburner, value.data);
|
|
}
|
|
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 and operations by 3%",
|
|
baseCost:5, costInc:2,
|
|
successChanceAll:3
|
|
});
|
|
Skills[SkillNames.Reaper] = new Skill({
|
|
name:SkillNames.Reaper,
|
|
desc:"Each level of this skill increases your " +
|
|
"effective combat stats for Bladeburner actions by 3%",
|
|
baseCost:3, costInc:2,
|
|
effStr:3, effDef:3, effDex:3, effAgi:3
|
|
});
|
|
Skills[SkillNames.Cloak] = new Skill({
|
|
name:SkillNames.Cloak,
|
|
desc:"Each level of this skill increases your " +
|
|
"success chance in stealth-related contracts and operations by 5.5%",
|
|
baseCost:3, costInc:1,
|
|
successChanceStealth:5.5
|
|
});
|
|
|
|
//TODO Marksman
|
|
//TODO Weapon Proficiency
|
|
|
|
Skills[SkillNames.Overclock] = new Skill({
|
|
name:SkillNames.Overclock,
|
|
desc:"Each level of this skill decreases the time it takes " +
|
|
"to attempt a contract or operation by 1% (Max Level: 95)",
|
|
baseCost:5, costInc:1, maxLvl:95,
|
|
actionTime:1
|
|
});
|
|
Skills[SkillNames.EvasiveSystem] = new Skill({
|
|
name:SkillNames.EvasiveSystem,
|
|
desc:"Each level of this skill increases your effective " +
|
|
"dexterity and agility for Bladeburner actions by 5%",
|
|
baseCost:2, costInc: 1,
|
|
effDex:5, effAgi:5
|
|
});
|
|
Skills[SkillNames.ShortCircuit] = new Skill({
|
|
name:SkillNames.ShortCircuit,
|
|
desc:"Each level of this skill increases your success chance " +
|
|
"in contracts and operations that involve retirement by 5.5%",
|
|
baseCost:3, costInc:2,
|
|
successChanceKill:5.5
|
|
});
|
|
Skills[SkillNames.DigitalObserver] = new Skill({
|
|
name:SkillNames.DigitalObserver,
|
|
desc:"Each level of this skill increases your success chance in " +
|
|
"all operations by 4%",
|
|
baseCost:5, costInc:2,
|
|
successChanceOperation: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.Tracer] = new Skill({
|
|
name:SkillNames.Tracer,
|
|
desc:"Each level of this skill increases your success chance in " +
|
|
"all contracts by 4%",
|
|
baseCost:3, costInc:2,
|
|
successChanceContract:4
|
|
});
|
|
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
|
|
});
|
|
|
|
//General Actions
|
|
var 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."
|
|
});
|
|
|
|
var 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.<br><br>" +
|
|
"Does NOT require stamina."
|
|
});
|
|
|
|
var actionName = "Recruitment";
|
|
GeneralActions[actionName] = new Action({
|
|
name:actionName,
|
|
desc:"Attempt to recruit members for your Bladeburner team. These members " +
|
|
"can help you conduct operations.<br><br>" +
|
|
"Does NOT require 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.<br><br>" +
|
|
"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:25, 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.<br><br>" +
|
|
"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:30, 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.<br><br>" +
|
|
"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:30, 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.<br><br>" +
|
|
"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:40, 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.<br><br>" +
|
|
"Your task is to intercept the deal. Leave no survivors.",
|
|
baseDifficulty:5000, reqdRank:12.5e3,
|
|
rankGain:40, 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.<br><br>" +
|
|
"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:50, 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.<br><br>" +
|
|
"Your mission is to eradicate Juggernaut and his followers.",
|
|
baseDifficulty:10e3, reqdRank:20e3,
|
|
rankGain:75, 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.<br><br>" +
|
|
"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:100, 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.<br><br>" +
|
|
"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:120, 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:150, 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.<br><br>" +
|
|
"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:200, 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.<br><br>" +
|
|
"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:500, 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.<br><br>" +
|
|
"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:1e3, 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.<br><br>" +
|
|
"I do not need to remind you why sentient-level AIs pose a serious " +
|
|
"thread to all of mankind.<br><br>" +
|
|
"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:2e3, 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.<br><br>" +
|
|
"The mission is to destroy this broadcast tower. Speed and " +
|
|
"stealth are of the upmost important for this.",
|
|
baseDifficulty:45e3, reqdRank:150e3,
|
|
rankGain:5e3, 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.<br><br>" +
|
|
"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: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.<br><br>" +
|
|
"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.<br><br>" +
|
|
"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).<br><br>" +
|
|
"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)<br><br>" +
|
|
"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.<br><br>" +
|
|
"It seems that the universe is not without a sense of irony.<br><br>" +
|
|
"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)<br><br>" +
|
|
"The bits are all around us. The daemons that hold the Node " +
|
|
"together can manifest themselves in many different ways.<br><br>" +
|
|
"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};
|