diff --git a/src/Bladeburner/Bladeburner.ts b/src/Bladeburner/Bladeburner.ts
index 3fb3f6536..00bb64764 100644
--- a/src/Bladeburner/Bladeburner.ts
+++ b/src/Bladeburner/Bladeburner.ts
@@ -1,7 +1,3 @@
-/*
- Here we have a bunch of functions converted to typescript, eventually they
- will go back into a Bladeburner class.
-*/
import {
Reviver,
Generic_toJSON,
@@ -97,122 +93,1879 @@ export class Bladeburner implements IBladeburner {
this.cities[BladeburnerConstants.CityNames[i]] = new City(BladeburnerConstants.CityNames[i]);
}
- updateSkillMultipliers(this); // Calls resetSkillMultipliers()
+ this.updateSkillMultipliers(); // Calls resetSkillMultipliers()
// Max Stamina is based on stats and Bladeburner-specific bonuses
if(player)
- calculateMaxStamina(this, player);
+ this.calculateMaxStamina(player);
this.stamina = this.maxStamina;
- create(this);
+ this.create();
}
getCurrentCity(): City {
- return getCurrentCity(this);
+ const city = this.cities[this.city];
+ if (!(city instanceof City)) {
+ throw new Error("Bladeburner.getCurrentCity() did not properly return a City object");
+ }
+ return city;
}
calculateStaminaPenalty(): number {
- return calculateStaminaPenalty(this);
+ return Math.min(1, this.stamina / (0.5 * this.maxStamina));
}
- startAction(player: IPlayer, action: IActionIdentifier): void {
- startAction(this, player, action);
+ startAction(player: IPlayer, actionId: IActionIdentifier): void {
+ if (actionId == null) return;
+ this.action = actionId;
+ this.actionTimeCurrent = 0;
+ switch (actionId.type) {
+ case ActionTypes["Idle"]:
+ this.actionTimeToComplete = 0;
+ break;
+ case ActionTypes["Contract"]:
+ try {
+ const action = this.getActionObject(actionId);
+ if (action == null) {
+ throw new Error("Failed to get Contract Object for: " + actionId.name);
+ }
+ if (action.count < 1) {return this.resetAction();}
+ this.actionTimeToComplete = action.getActionTime(this);
+ } catch(e) {
+ exceptionAlert(e);
+ }
+ break;
+ case ActionTypes["Operation"]: {
+ try {
+ const action = this.getActionObject(actionId);
+ if (action == null) {
+ throw new Error ("Failed to get Operation Object for: " + actionId.name);
+ }
+ if (action.count < 1) {return this.resetAction();}
+ if (actionId.name === "Raid" && this.getCurrentCity().commsEst === 0) {return this.resetAction();}
+ this.actionTimeToComplete = action.getActionTime(this);
+ } catch(e) {
+ exceptionAlert(e);
+ }
+ break;
+ }
+ case ActionTypes["BlackOp"]:
+ case ActionTypes["BlackOperation"]: {
+ try {
+ // Safety measure - don't repeat BlackOps that are already done
+ if (this.blackops[actionId.name] != null) {
+ this.resetAction();
+ this.log("Error: Tried to start a Black Operation that had already been completed");
+ break;
+ }
+
+ const action = this.getActionObject(actionId);
+ if (action == null) {
+ throw new Error("Failed to get BlackOperation object for: " + actionId.name);
+ }
+ this.actionTimeToComplete = action.getActionTime(this);
+ } catch(e) {
+ exceptionAlert(e);
+ }
+ break;
+ }
+ case ActionTypes["Recruitment"]:
+ this.actionTimeToComplete = this.getRecruitmentTime(player);
+ break;
+ case ActionTypes["Training"]:
+ case ActionTypes["FieldAnalysis"]:
+ case ActionTypes["Field Analysis"]:
+ this.actionTimeToComplete = 30;
+ break;
+ case ActionTypes["Diplomacy"]:
+ case ActionTypes["Hyperbolic Regeneration Chamber"]:
+ this.actionTimeToComplete = 60;
+ break;
+ default:
+ throw new Error("Invalid Action Type in startAction(Bladeburner,player, ): " + actionId.type);
+ break;
+ }
}
upgradeSkill(skill: Skill): void {
- upgradeSkill(this, skill);
+ // This does NOT handle deduction of skill points
+ const skillName = skill.name;
+ if (this.skills[skillName]) {
+ ++this.skills[skillName];
+ } else {
+ this.skills[skillName] = 1;
+ }
+ if (isNaN(this.skills[skillName]) || this.skills[skillName] < 0) {
+ throw new Error("Level of Skill " + skillName + " is invalid: " + this.skills[skillName]);
+ }
+ this.updateSkillMultipliers();
}
- executeConsoleCommands(player: IPlayer, command: string): void {
- executeConsoleCommands(this, player, command);
+ executeConsoleCommands(player: IPlayer, commands: string): void {
+ try {
+ // Console History
+ if (this.consoleHistory[this.consoleHistory.length-1] != commands) {
+ this.consoleHistory.push(commands);
+ if (this.consoleHistory.length > 50) {
+ this.consoleHistory.splice(0, 1);
+ }
+ }
+
+ const arrayOfCommands = commands.split(";");
+ for (let i = 0; i < arrayOfCommands.length; ++i) {
+ this.executeConsoleCommand(player, arrayOfCommands[i]);
+ }
+ } catch(e) {
+ exceptionAlert(e);
+ }
}
postToConsole(input: string, saveToLogs?: boolean): void {
- postToConsole(this, input, saveToLogs);
+ const MaxConsoleEntries = 100;
+ if (saveToLogs) {
+ this.consoleLogs.push(input);
+ if (this.consoleLogs.length > MaxConsoleEntries) {
+ this.consoleLogs.shift();
+ }
+ }
}
log(input: string): void {
- log(this, input);
+ // Adds a timestamp and then just calls postToConsole
+ this.postToConsole(`[${getTimestamp()}] ${input}`);
}
resetAction(): void {
- resetAction(this);
+ this.action = new ActionIdentifier({type:ActionTypes.Idle});
}
clearConsole(): void {
- clearConsole(this);
+ this.consoleLogs.length = 0;
}
prestige(): void {
- prestige(this);
+ this.resetAction();
+ const bladeburnerFac = Factions["Bladeburners"];
+ if (this.rank >= BladeburnerConstants.RankNeededForFaction) {
+ joinFaction(bladeburnerFac);
+ }
}
- storeCycles(numCycles?: number): void {
- storeCycles(this, numCycles);
+ storeCycles(numCycles: number = 0): void {
+ this.storedCycles += numCycles;
+ }
+
+
+ // working on
+ getActionIdFromTypeAndName(type: string = "", name: string = ""): IActionIdentifier | null {
+ if (type === "" || name === "") {return null;}
+ const action = new ActionIdentifier();
+ const convertedType = type.toLowerCase().trim();
+ const convertedName = name.toLowerCase().trim();
+ switch (convertedType) {
+ case "contract":
+ case "contracts":
+ case "contr":
+ action.type = ActionTypes["Contract"];
+ if (this.contracts.hasOwnProperty(name)) {
+ action.name = name;
+ return action;
+ } else {
+ return null;
+ }
+ break;
+ case "operation":
+ case "operations":
+ case "op":
+ case "ops":
+ action.type = ActionTypes["Operation"];
+ if (this.operations.hasOwnProperty(name)) {
+ action.name = name;
+ return action;
+ } else {
+ return null;
+ }
+ break;
+ case "blackoperation":
+ case "black operation":
+ case "black operations":
+ case "black op":
+ case "black ops":
+ case "blackop":
+ case "blackops":
+ action.type = ActionTypes["BlackOp"];
+ if (BlackOperations.hasOwnProperty(name)) {
+ action.name = name;
+ return action;
+ } else {
+ return null;
+ }
+ break;
+ case "general":
+ case "general action":
+ case "gen":
+ break;
+ default:
+ return null;
+ }
+
+ if (convertedType.startsWith("gen")) {
+ switch (convertedName) {
+ case "training":
+ action.type = ActionTypes["Training"];
+ action.name = "Training";
+ break;
+ case "recruitment":
+ case "recruit":
+ action.type = ActionTypes["Recruitment"];
+ action.name = "Recruitment";
+ break;
+ case "field analysis":
+ case "fieldanalysis":
+ action.type = ActionTypes["Field Analysis"];
+ action.name = "Field Analysis";
+ break;
+ case "diplomacy":
+ action.type = ActionTypes["Diplomacy"];
+ action.name = "Diplomacy";
+ break;
+ case "hyperbolic regeneration chamber":
+ action.type = ActionTypes["Hyperbolic Regeneration Chamber"];
+ action.name = "Hyperbolic Regeneration Chamber";
+ break;
+ default:
+ return null;
+ }
+ return action;
+ }
+
+ return null;
+ }
+
+ executeStartConsoleCommand(player: IPlayer, args: string[]): void {
+ if (args.length !== 3) {
+ this.postToConsole("Invalid usage of 'start' console command: start [type] [name]");
+ this.postToConsole("Use 'help start' for more info");
+ return;
+ }
+ const name = args[2];
+ switch (args[1].toLowerCase()) {
+ case "general":
+ case "gen":
+ if (GeneralActions[name] != null) {
+ this.action.type = ActionTypes[name];
+ this.action.name = name;
+ this.startAction(player, this.action);
+ } else {
+ this.postToConsole("Invalid action name specified: " + args[2]);
+ }
+ break;
+ case "contract":
+ case "contracts":
+ if (this.contracts[name] != null) {
+ this.action.type = ActionTypes.Contract;
+ this.action.name = name;
+ this.startAction(player, this.action);
+ } else {
+ this.postToConsole("Invalid contract name specified: " + args[2]);
+ }
+ break;
+ case "ops":
+ case "op":
+ case "operations":
+ case "operation":
+ if (this.operations[name] != null) {
+ this.action.type = ActionTypes.Operation;
+ this.action.name = name;
+ this.startAction(player, this.action);
+ } else {
+ this.postToConsole("Invalid Operation name specified: " + args[2]);
+ }
+ break;
+ case "blackops":
+ case "blackop":
+ case "black operations":
+ case "black operation":
+ if (BlackOperations[name] != null) {
+ this.action.type = ActionTypes.BlackOperation;
+ this.action.name = name;
+ this.startAction(player, this.action);
+ } else {
+ this.postToConsole("Invalid BlackOp name specified: " + args[2]);
+ }
+ break;
+ default:
+ this.postToConsole("Invalid action/event type specified: " + args[1]);
+ this.postToConsole("Examples of valid action/event identifiers are: [general, contract, op, blackop]");
+ break;
+ }
+ }
+
+ executeSkillConsoleCommand(args: string[]): void {
+ switch (args.length) {
+ case 1:
+ // Display Skill Help Command
+ this.postToConsole("Invalid usage of 'skill' console command: skill [action] [name]");
+ this.postToConsole("Use 'help skill' for more info");
+ break;
+ case 2:
+ if (args[1].toLowerCase() === "list") {
+ // List all skills and their level
+ this.postToConsole("Skills: ");
+ const skillNames = Object.keys(Skills);
+ for(let i = 0; i < skillNames.length; ++i) {
+ let skill = Skills[skillNames[i]];
+ let level = 0;
+ if (this.skills[skill.name] != null) {level = this.skills[skill.name];}
+ this.postToConsole(skill.name + ": Level " + formatNumber(level, 0));
+ }
+ this.postToConsole(" ");
+ this.postToConsole("Effects: ");
+ const multKeys = Object.keys(this.skillMultipliers);
+ for (let i = 0; i < multKeys.length; ++i) {
+ let mult = this.skillMultipliers[multKeys[i]];
+ if (mult && mult !== 1) {
+ mult = formatNumber(mult, 3);
+ switch(multKeys[i]) {
+ case "successChanceAll":
+ this.postToConsole("Total Success Chance: x" + mult);
+ break;
+ case "successChanceStealth":
+ this.postToConsole("Stealth Success Chance: x" + mult);
+ break;
+ case "successChanceKill":
+ this.postToConsole("Retirement Success Chance: x" + mult);
+ break;
+ case "successChanceContract":
+ this.postToConsole("Contract Success Chance: x" + mult);
+ break;
+ case "successChanceOperation":
+ this.postToConsole("Operation Success Chance: x" + mult);
+ break;
+ case "successChanceEstimate":
+ this.postToConsole("Synthoid Data Estimate: x" + mult);
+ break;
+ case "actionTime":
+ this.postToConsole("Action Time: x" + mult);
+ break;
+ case "effHack":
+ this.postToConsole("Hacking Skill: x" + mult);
+ break;
+ case "effStr":
+ this.postToConsole("Strength: x" + mult);
+ break;
+ case "effDef":
+ this.postToConsole("Defense: x" + mult);
+ break;
+ case "effDex":
+ this.postToConsole("Dexterity: x" + mult);
+ break;
+ case "effAgi":
+ this.postToConsole("Agility: x" + mult);
+ break;
+ case "effCha":
+ this.postToConsole("Charisma: x" + mult);
+ break;
+ case "effInt":
+ this.postToConsole("Intelligence: x" + mult);
+ break;
+ case "stamina":
+ this.postToConsole("Stamina: x" + mult);
+ break;
+ default:
+ console.warn(`Unrecognized SkillMult Key: ${multKeys[i]}`);
+ break;
+ }
+ }
+ }
+ } else {
+ this.postToConsole("Invalid usage of 'skill' console command: skill [action] [name]");
+ this.postToConsole("Use 'help skill' for more info");
+ }
+ break;
+ case 3:
+ const skillName = args[2];
+ const skill = Skills[skillName];
+ if (skill == null || !(skill instanceof Skill)) {
+ this.postToConsole("Invalid skill name (Note that it is case-sensitive): " + skillName);
+ }
+ if (args[1].toLowerCase() === "list") {
+ let level = 0;
+ if (this.skills[skill.name] !== undefined) {
+ level = this.skills[skill.name];
+ }
+ this.postToConsole(skill.name + ": Level " + formatNumber(level));
+ } else if (args[1].toLowerCase() === "level") {
+ let currentLevel = 0;
+ if (this.skills[skillName] && !isNaN(this.skills[skillName])) {
+ currentLevel = this.skills[skillName];
+ }
+ const pointCost = skill.calculateCost(currentLevel);
+ if (this.skillPoints >= pointCost) {
+ this.skillPoints -= pointCost;
+ this.upgradeSkill(skill);
+ this.log(skill.name + " upgraded to Level " + this.skills[skillName]);
+ } else {
+ this.postToConsole("You do not have enough Skill Points to upgrade this. You need " + formatNumber(pointCost, 0));
+ }
+
+ } else {
+ this.postToConsole("Invalid usage of 'skill' console command: skill [action] [name]");
+ this.postToConsole("Use 'help skill' for more info");
+ }
+ break;
+ default:
+ this.postToConsole("Invalid usage of 'skill' console command: skill [action] [name]");
+ this.postToConsole("Use 'help skill' for more info");
+ break;
+ }
+ }
+
+ executeLogConsoleCommand(args: string[]): void {
+ if (args.length < 3) {
+ this.postToConsole("Invalid usage of log command: log [enable/disable] [action/event]");
+ this.postToConsole("Use 'help log' for more details and examples");
+ return;
+ }
+
+ let flag = true;
+ if (args[1].toLowerCase().includes("d")) {flag = false;} // d for disable
+
+ switch (args[2].toLowerCase()) {
+ case "general":
+ case "gen":
+ this.logging.general = flag;
+ this.log("Logging " + (flag ? "enabled" : "disabled") + " for general actions");
+ break;
+ case "contract":
+ case "contracts":
+ this.logging.contracts = flag;
+ this.log("Logging " + (flag ? "enabled" : "disabled") + " for Contracts");
+ break;
+ case "ops":
+ case "op":
+ case "operations":
+ case "operation":
+ this.logging.ops = flag;
+ this.log("Logging " + (flag ? "enabled" : "disabled") + " for Operations");
+ break;
+ case "blackops":
+ case "blackop":
+ case "black operations":
+ case "black operation":
+ this.logging.blackops = flag;
+ this.log("Logging " + (flag ? "enabled" : "disabled") + " for BlackOps");
+ break;
+ case "event":
+ case "events":
+ this.logging.events = flag;
+ this.log("Logging " + (flag ? "enabled" : "disabled") + " for events");
+ break;
+ case "all":
+ this.logging.general = flag;
+ this.logging.contracts = flag;
+ this.logging.ops = flag;
+ this.logging.blackops = flag;
+ this.logging.events = flag;
+ this.log("Logging " + (flag ? "enabled" : "disabled") + " for everything");
+ break;
+ default:
+ this.postToConsole("Invalid action/event type specified: " + args[2]);
+ this.postToConsole("Examples of valid action/event identifiers are: [general, contracts, ops, blackops, events]");
+ break;
+ }
+ }
+
+ executeHelpConsoleCommand(args: string[]): void {
+ if (args.length === 1) {
+ for(const line of ConsoleHelpText.helpList){
+ this.postToConsole(line);
+ }
+ } else {
+ for (let i = 1; i < args.length; ++i) {
+ if(!(args[i] in ConsoleHelpText)) continue;
+ const helpText = ConsoleHelpText[args[i]];
+ for(const line of helpText){
+ this.postToConsole(line);
+ }
+ }
+ }
+ }
+
+ executeAutomateConsoleCommand(args: string[]): void {
+ if (args.length !== 2 && args.length !== 4) {
+ this.postToConsole("Invalid use of 'automate' command: automate [var] [val] [hi/low]. Use 'help automate' for more info");
+ return;
+ }
+
+ // Enable/Disable
+ if (args.length === 2) {
+ const flag = args[1];
+ if (flag.toLowerCase() === "status") {
+ this.postToConsole("Automation: " + (this.automateEnabled ? "enabled" : "disabled"));
+ if (this.automateEnabled) {
+ this.postToConsole("When your stamina drops to " + formatNumber(this.automateThreshLow, 0) +
+ ", you will automatically switch to " + this.automateActionLow.name +
+ ". When your stamina recovers to " +
+ formatNumber(this.automateThreshHigh, 0) + ", you will automatically " +
+ "switch to " + this.automateActionHigh.name + ".");
+ }
+
+ } else if (flag.toLowerCase().includes("en")) {
+ if (!(this.automateActionLow instanceof ActionIdentifier) ||
+ !(this.automateActionHigh instanceof ActionIdentifier)) {
+ return this.log("Failed to enable automation. Actions were not set");
+ }
+ this.automateEnabled = true;
+ this.log("Bladeburner automation enabled");
+ } else if (flag.toLowerCase().includes("d")) {
+ this.automateEnabled = false;
+ this.log("Bladeburner automation disabled");
+ } else {
+ this.log("Invalid argument for 'automate' console command: " + args[1]);
+ }
+ return;
+ }
+
+ // Set variables
+ if (args.length === 4) {
+ const variable = args[1];
+ const val = args[2];
+
+ let highLow = false; // True for high, false for low
+ if (args[3].toLowerCase().includes("hi")) {highLow = true;}
+
+ switch (variable) {
+ case "general":
+ case "gen":
+ if (GeneralActions[val] != null) {
+ const action = new ActionIdentifier({
+ type:ActionTypes[val], name:val,
+ });
+ if (highLow) {
+ this.automateActionHigh = action;
+ } else {
+ this.automateActionLow = action;
+ }
+ this.log("Automate (" + (highLow ? "HIGH" : "LOW") + ") action set to " + val);
+ } else {
+ this.postToConsole("Invalid action name specified: " + val);
+ }
+ break;
+ case "contract":
+ case "contracts":
+ if (this.contracts[val] != null) {
+ const action = new ActionIdentifier({
+ type:ActionTypes.Contract, name:val,
+ });
+ if (highLow) {
+ this.automateActionHigh = action;
+ } else {
+ this.automateActionLow = action;
+ }
+ this.log("Automate (" + (highLow ? "HIGH" : "LOW") + ") action set to " + val);
+ } else {
+ this.postToConsole("Invalid contract name specified: " + val);
+ }
+ break;
+ case "ops":
+ case "op":
+ case "operations":
+ case "operation":
+ if (this.operations[val] != null) {
+ const action = new ActionIdentifier({
+ type:ActionTypes.Operation, name:val,
+ });
+ if (highLow) {
+ this.automateActionHigh = action;
+ } else {
+ this.automateActionLow = action;
+ }
+ this.log("Automate (" + (highLow ? "HIGH" : "LOW") + ") action set to " + val);
+ } else {
+ this.postToConsole("Invalid Operation name specified: " + val);
+ }
+ break;
+ case "stamina":
+ if (isNaN(parseFloat(val))) {
+ this.postToConsole("Invalid value specified for stamina threshold (must be numeric): " + val);
+ } else {
+ if (highLow) {
+ this.automateThreshHigh = Number(val);
+ } else {
+ this.automateThreshLow = Number(val);
+ }
+ this.log("Automate (" + (highLow ? "HIGH" : "LOW") + ") stamina threshold set to " + val);
+ }
+ break;
+ default:
+ break;
+ }
+
+ return;
+ }
+ }
+
+ parseCommandArguments(command: string): string[] {
+ /**
+ * Returns an array with command and its arguments in each index.
+ * e.g. skill "blade's intuition" foo returns [skill, blade's intuition, foo]
+ * The input to the fn will be trimmed and will have all whitespace replaced w/ a single space
+ */
+ const args = [];
+ let start = 0;
+ let i = 0;
+ while (i < command.length) {
+ const c = command.charAt(i);
+ if (c === '"') { // Double quotes
+ const endQuote = command.indexOf('"', i+1);
+ if (endQuote !== -1 && (endQuote === command.length-1 || command.charAt(endQuote+1) === " ")) {
+ args.push(command.substr(i+1, (endQuote - i - 1)));
+ if (endQuote === command.length-1) {
+ start = i = endQuote+1;
+ } else {
+ start = i = endQuote+2; // Skip the space
+ }
+ continue;
+ }
+ } else if (c === "'") { // Single quotes, same thing as above
+ const endQuote = command.indexOf("'", i+1);
+ if (endQuote !== -1 && (endQuote === command.length-1 || command.charAt(endQuote+1) === " ")) {
+ args.push(command.substr(i+1, (endQuote - i - 1)));
+ if (endQuote === command.length-1) {
+ start = i = endQuote+1;
+ } else {
+ start = i = endQuote+2; // Skip the space
+ }
+ continue;
+ }
+ } else if (c === " ") {
+ args.push(command.substr(start, i-start));
+ start = i+1;
+ }
+ ++i;
+ }
+ if (start !== i) {args.push(command.substr(start, i-start));}
+ return args;
+ }
+
+ executeConsoleCommand(player: IPlayer, command: string) {
+ command = command.trim();
+ command = command.replace(/\s\s+/g, ' '); // Replace all whitespace w/ a single space
+
+ const args = this.parseCommandArguments(command);
+ if (args.length <= 0) return; // Log an error?
+
+ switch(args[0].toLowerCase()) {
+ case "automate":
+ this.executeAutomateConsoleCommand(args);
+ break;
+ case "clear":
+ case "cls":
+ this.clearConsole();
+ break;
+ case "help":
+ this.executeHelpConsoleCommand(args);
+ break;
+ case "log":
+ this.executeLogConsoleCommand(args);
+ break;
+ case "skill":
+ this.executeSkillConsoleCommand(args);
+ break;
+ case "start":
+ this.executeStartConsoleCommand(player, args);
+ break;
+ case "stop":
+ this.resetAction();
+ break;
+ default:
+ this.postToConsole("Invalid console command");
+ break;
+ }
+ }
+
+ triggerMigration(sourceCityName: string): void {
+ let destCityName = BladeburnerConstants.CityNames[getRandomInt(0, 5)];
+ while (destCityName === sourceCityName) {
+ destCityName = BladeburnerConstants.CityNames[getRandomInt(0, 5)];
+ }
+ const destCity = this.cities[destCityName];
+ const sourceCity = this.cities[sourceCityName];
+ if (destCity == null || sourceCity == null) {
+ throw new Error("Failed to find City with name: " + destCityName);
+ }
+ const rand = Math.random();
+ let percentage = getRandomInt(3, 15) / 100;
+
+ if (rand < 0.05 && sourceCity.comms > 0) { // 5% chance for community migration
+ percentage *= getRandomInt(2, 4); // Migration increases population change
+ --sourceCity.comms;
+ ++destCity.comms;
+ }
+ const count = Math.round(sourceCity.pop * percentage);
+ sourceCity.pop -= count;
+ destCity.pop += count;
+ }
+
+ triggerPotentialMigration(sourceCityName: string, chance: number): void {
+ if (chance == null || isNaN(chance)) {
+ console.error("Invalid 'chance' parameter passed into Bladeburner.triggerPotentialMigration()");
+ }
+ if (chance > 1) {chance /= 100;}
+ if (Math.random() < chance) {this.triggerMigration(sourceCityName);}
+ }
+
+ randomEvent(): void {
+ const chance = Math.random();
+
+ // Choose random source/destination city for events
+ const sourceCityName = BladeburnerConstants.CityNames[getRandomInt(0, 5)];
+ const sourceCity = this.cities[sourceCityName];
+ if (!(sourceCity instanceof City)) {
+ throw new Error("sourceCity was not a City object in Bladeburner.randomEvent()");
+ }
+
+ let destCityName = BladeburnerConstants.CityNames[getRandomInt(0, 5)];
+ while (destCityName === sourceCityName) {
+ destCityName = BladeburnerConstants.CityNames[getRandomInt(0, 5)];
+ }
+ const destCity = this.cities[destCityName];
+
+ if (!(sourceCity instanceof City) || !(destCity instanceof City)) {
+ throw new Error("sourceCity/destCity was not a City object in Bladeburner.randomEvent()");
+ }
+
+ if (chance <= 0.05) {
+ // New Synthoid Community, 5%
+ ++sourceCity.comms;
+ const percentage = getRandomInt(10, 20) / 100;
+ const count = Math.round(sourceCity.pop * percentage);
+ sourceCity.pop += count;
+ if (this.logging.events) {
+ this.log("Intelligence indicates that a new Synthoid community was formed in a city");
+ }
+ } else if (chance <= 0.1) {
+ // Synthoid Community Migration, 5%
+ if (sourceCity.comms <= 0) {
+ // If no comms in source city, then instead trigger a new Synthoid community event
+ ++sourceCity.comms;
+ const percentage = getRandomInt(10, 20) / 100;
+ const count = Math.round(sourceCity.pop * percentage);
+ sourceCity.pop += count;
+ if (this.logging.events) {
+ this.log("Intelligence indicates that a new Synthoid community was formed in a city");
+ }
+ } else {
+ --sourceCity.comms;
+ ++destCity.comms;
+
+ // Change pop
+ const percentage = getRandomInt(10, 20) / 100;
+ const count = Math.round(sourceCity.pop * percentage);
+ sourceCity.pop -= count;
+ destCity.pop += count;
+
+ if (this.logging.events) {
+ this.log("Intelligence indicates that a Synthoid community migrated from " + sourceCityName + " to some other city");
+ }
+ }
+ } else if (chance <= 0.3) {
+ // New Synthoids (non community), 20%
+ const percentage = getRandomInt(8, 24) / 100;
+ const count = Math.round(sourceCity.pop * percentage);
+ sourceCity.pop += count;
+ if (this.logging.events) {
+ this.log("Intelligence indicates that the Synthoid population of " + sourceCityName + " just changed significantly");
+ }
+ } else if (chance <= 0.5) {
+ // Synthoid migration (non community) 20%
+ this.triggerMigration(sourceCityName);
+ if (this.logging.events) {
+ this.log("Intelligence indicates that a large number of Synthoids migrated from " + sourceCityName + " to some other city");
+ }
+ } else if (chance <= 0.7) {
+ // Synthoid Riots (+chaos), 20%
+ sourceCity.chaos += 1;
+ sourceCity.chaos *= (1 + getRandomInt(5, 20) / 100);
+ if (this.logging.events) {
+ this.log("Tensions between Synthoids and humans lead to riots in " + sourceCityName + "! Chaos increased");
+ }
+ } else if (chance <= 0.9) {
+ // Less Synthoids, 20%
+ const percentage = getRandomInt(8, 20) / 100;
+ const count = Math.round(sourceCity.pop * percentage);
+ sourceCity.pop -= count;
+ if (this.logging.events) {
+ this.log("Intelligence indicates that the Synthoid population of " + sourceCityName + " just changed significantly");
+ }
+ }
+ // 10% chance of nothing happening
+ }
+
+ /**
+ * Process stat gains from Contracts, Operations, and Black Operations
+ * @param action(Action obj) - Derived action class
+ * @param success(bool) - Whether action was successful
+ */
+ gainActionStats(player: IPlayer, action: IAction, success: boolean): void {
+ const difficulty = action.getDifficulty();
+
+ /**
+ * Gain multiplier based on difficulty. If it changes then the
+ * same variable calculated in completeAction() needs to change too
+ */
+ const difficultyMult = Math.pow(difficulty, BladeburnerConstants.DiffMultExponentialFactor) + difficulty / BladeburnerConstants.DiffMultLinearFactor;
+
+ const time = this.actionTimeToComplete;
+ const successMult = success ? 1 : 0.5;
+
+ const unweightedGain = time * BladeburnerConstants.BaseStatGain * successMult * difficultyMult;
+ const unweightedIntGain = time * BladeburnerConstants.BaseIntGain * successMult * difficultyMult;
+ const skillMult = this.skillMultipliers.expGain;
+ player.gainHackingExp(unweightedGain * action.weights.hack * player.hacking_exp_mult * skillMult);
+ player.gainStrengthExp(unweightedGain * action.weights.str * player.strength_exp_mult * skillMult);
+ player.gainDefenseExp(unweightedGain * action.weights.def * player.defense_exp_mult * skillMult);
+ player.gainDexterityExp(unweightedGain * action.weights.dex * player.dexterity_exp_mult * skillMult);
+ player.gainAgilityExp(unweightedGain * action.weights.agi * player.agility_exp_mult * skillMult);
+ player.gainCharismaExp(unweightedGain * action.weights.cha * player.charisma_exp_mult * skillMult);
+ let intExp = unweightedIntGain * action.weights.int * skillMult;
+ if (intExp > 1) {
+ intExp = Math.pow(intExp, 0.8);
+ }
+ player.gainIntelligenceExp(intExp);
+ }
+
+ getDiplomacyEffectiveness(player: IPlayer): number {
+ // Returns a decimal by which the city's chaos level should be multiplied (e.g. 0.98)
+ const CharismaLinearFactor = 1e3;
+ const CharismaExponentialFactor = 0.045;
+
+ const charismaEff = Math.pow(player.charisma, CharismaExponentialFactor) + player.charisma / CharismaLinearFactor;
+ return (100 - charismaEff) / 100;
+ }
+
+ getRecruitmentSuccessChance(player: IPlayer): number {
+ return Math.pow(player.charisma, 0.45) / (this.teamSize + 1);
+ }
+
+ getRecruitmentTime(player: IPlayer): number {
+ const effCharisma = player.charisma * this.skillMultipliers.effCha;
+ const charismaFactor = Math.pow(effCharisma, 0.81) + effCharisma / 90;
+ return Math.max(10, Math.round(BladeburnerConstants.BaseRecruitmentTimeNeeded - charismaFactor));
+ }
+
+ resetSkillMultipliers(): void {
+ this.skillMultipliers = {
+ successChanceAll: 1,
+ successChanceStealth: 1,
+ successChanceKill: 1,
+ successChanceContract: 1,
+ successChanceOperation: 1,
+ successChanceEstimate: 1,
+ actionTime: 1,
+ effHack: 1,
+ effStr: 1,
+ effDef: 1,
+ effDex: 1,
+ effAgi: 1,
+ effCha: 1,
+ effInt: 1,
+ stamina: 1,
+ money: 1,
+ expGain: 1,
+ };
+ }
+
+ updateSkillMultipliers(): void {
+ this.resetSkillMultipliers();
+ for (const skillName in this.skills) {
+ if (this.skills.hasOwnProperty(skillName)) {
+ const skill = Skills[skillName];
+ if (skill == null) {
+ throw new Error("Could not find Skill Object for: " + skillName);
+ }
+ const level = this.skills[skillName];
+ if (level == null || level <= 0) {continue;} //Not upgraded
+
+ const multiplierNames = Object.keys(this.skillMultipliers);
+ for (let i = 0; i < multiplierNames.length; ++i) {
+ const multiplierName = multiplierNames[i];
+ if (skill.getMultiplier(multiplierName) != null && !isNaN(skill.getMultiplier(multiplierName))) {
+ const value = skill.getMultiplier(multiplierName) * level;
+ let multiplierValue = 1 + (value / 100);
+ if (multiplierName === "actionTime") {
+ multiplierValue = 1 - (value / 100);
+ }
+ this.skillMultipliers[multiplierName] *= multiplierValue;
+ }
+ }
+ }
+ }
+ }
+
+ completeOperation(success: boolean): void {
+ if (this.action.type !== ActionTypes.Operation) {
+ throw new Error("completeOperation() called even though current action is not an Operation");
+ }
+ const action = this.getActionObject(this.action);
+ if (action == null) {
+ throw new Error("Failed to get Contract/Operation Object for: " + this.action.name);
+ }
+
+ // Calculate team losses
+ const teamCount = action.teamCount;
+ if (teamCount >= 1) {
+ let max;
+ if (success) {
+ max = Math.ceil(teamCount/2);
+ } else {
+ max = Math.floor(teamCount)
+ }
+ const losses = getRandomInt(0, max);
+ this.teamSize -= losses;
+ this.teamLost += losses;
+ if (this.logging.ops && losses > 0) {
+ this.log("Lost " + formatNumber(losses, 0) + " team members during this " + action.name);
+ }
+ }
+
+ const city = this.getCurrentCity();
+ switch (action.name) {
+ case "Investigation":
+ if (success) {
+ city.improvePopulationEstimateByPercentage(0.4 * this.skillMultipliers.successChanceEstimate);
+ if (Math.random() < (0.02 * this.skillMultipliers.successChanceEstimate)) {
+ city.improveCommunityEstimate(1);
+ }
+ } else {
+ this.triggerPotentialMigration(this.city, 0.1);
+ }
+ break;
+ case "Undercover Operation":
+ if (success) {
+ city.improvePopulationEstimateByPercentage(0.8 * this.skillMultipliers.successChanceEstimate);
+ if (Math.random() < (0.02 * this.skillMultipliers.successChanceEstimate)) {
+ city.improveCommunityEstimate(1);
+ }
+ } else {
+ this.triggerPotentialMigration(this.city, 0.15);
+ }
+ break;
+ case "Sting Operation":
+ if (success) {
+ city.changePopulationByPercentage(-0.1, {changeEstEqually:true, nonZero:true});
+ }
+ city.changeChaosByCount(0.1);
+ break;
+ case "Raid":
+ if (success) {
+ city.changePopulationByPercentage(-1, {changeEstEqually:true, nonZero:true});
+ --city.comms;
+ --city.commsEst;
+ } else {
+ const change = getRandomInt(-10, -5) / 10;
+ city.changePopulationByPercentage(change, {nonZero:true, changeEstEqually:false});
+ }
+ city.changeChaosByPercentage(getRandomInt(1, 5));
+ break;
+ case "Stealth Retirement Operation":
+ if (success) {
+ city.changePopulationByPercentage(-0.5, {changeEstEqually:true,nonZero:true});
+ }
+ city.changeChaosByPercentage(getRandomInt(-3, -1));
+ break;
+ case "Assassination":
+ if (success) {
+ city.changePopulationByCount(-1, {estChange:-1, estOffset: 0});
+ }
+ city.changeChaosByPercentage(getRandomInt(-5, 5));
+ break;
+ default:
+ throw new Error("Invalid Action name in completeOperation: " + this.action.name);
+ }
+ }
+
+ getActionObject(actionId: IActionIdentifier): IAction | null {
+ /**
+ * Given an ActionIdentifier object, returns the corresponding
+ * GeneralAction, Contract, Operation, or BlackOperation object
+ */
+ switch (actionId.type) {
+ case ActionTypes["Contract"]:
+ return this.contracts[actionId.name];
+ case ActionTypes["Operation"]:
+ return this.operations[actionId.name];
+ case ActionTypes["BlackOp"]:
+ case ActionTypes["BlackOperation"]:
+ return BlackOperations[actionId.name];
+ case ActionTypes["Training"]:
+ return GeneralActions["Training"];
+ case ActionTypes["Field Analysis"]:
+ return GeneralActions["Field Analysis"];
+ case ActionTypes["Recruitment"]:
+ return GeneralActions["Recruitment"];
+ case ActionTypes["Diplomacy"]:
+ return GeneralActions["Diplomacy"];
+ case ActionTypes["Hyperbolic Regeneration Chamber"]:
+ return GeneralActions["Hyperbolic Regeneration Chamber"];
+ default:
+ return null;
+ }
+ }
+
+ completeContract(success: boolean): void {
+ if (this.action.type !== ActionTypes.Contract) {
+ throw new Error("completeContract() called even though current action is not a Contract");
+ }
+ var city = this.getCurrentCity();
+ if (success) {
+ switch (this.action.name) {
+ case "Tracking":
+ // Increase estimate accuracy by a relatively small amount
+ city.improvePopulationEstimateByCount(getRandomInt(100, 1e3));
+ break;
+ case "Bounty Hunter":
+ city.changePopulationByCount(-1, {estChange:-1, estOffset: 0});
+ city.changeChaosByCount(0.02);
+ break;
+ case "Retirement":
+ city.changePopulationByCount(-1, {estChange:-1, estOffset: 0});
+ city.changeChaosByCount(0.04);
+ break;
+ default:
+ throw new Error("Invalid Action name in completeContract: " + this.action.name);
+ }
+ }
+ }
+
+ completeAction(player: IPlayer): void {
+ switch (this.action.type) {
+ case ActionTypes["Contract"]:
+ case ActionTypes["Operation"]: {
+ try {
+ const isOperation = (this.action.type === ActionTypes["Operation"]);
+ const action = this.getActionObject(this.action);
+ if (action == null) {
+ throw new Error("Failed to get Contract/Operation Object for: " + this.action.name);
+ }
+ const difficulty = action.getDifficulty();
+ const difficultyMultiplier = Math.pow(difficulty, BladeburnerConstants.DiffMultExponentialFactor) + difficulty / BladeburnerConstants.DiffMultLinearFactor;
+ const rewardMultiplier = Math.pow(action.rewardFac, action.level-1);
+
+ // Stamina loss is based on difficulty
+ this.stamina -= (BladeburnerConstants.BaseStaminaLoss * difficultyMultiplier);
+ if (this.stamina < 0) {this.stamina = 0;}
+
+ // Process Contract/Operation success/failure
+ if (action.attempt(this)) {
+ this.gainActionStats(player, action, true);
+ ++action.successes;
+ --action.count;
+
+ // Earn money for contracts
+ let moneyGain = 0;
+ if (!isOperation) {
+ moneyGain = BladeburnerConstants.ContractBaseMoneyGain * rewardMultiplier * this.skillMultipliers.money;
+ player.gainMoney(moneyGain);
+ player.recordMoneySource(moneyGain, "bladeburner");
+ }
+
+ if (isOperation) {
+ action.setMaxLevel(BladeburnerConstants.OperationSuccessesPerLevel);
+ } else {
+ action.setMaxLevel(BladeburnerConstants.ContractSuccessesPerLevel);
+ }
+ if (action.rankGain) {
+ const gain = addOffset(action.rankGain * rewardMultiplier * BitNodeMultipliers.BladeburnerRank, 10);
+ this.changeRank(player, gain);
+ if (isOperation && this.logging.ops) {
+ this.log(action.name + " successfully completed! Gained " + formatNumber(gain, 3) + " rank");
+ } else if (!isOperation && this.logging.contracts) {
+ this.log(action.name + " contract successfully completed! Gained " + formatNumber(gain, 3) + " rank and " + numeralWrapper.formatMoney(moneyGain));
+ }
+ }
+ isOperation ? this.completeOperation(true) : this.completeContract(true);
+ } else {
+ this.gainActionStats(player, action, false);
+ ++action.failures;
+ let loss = 0, damage = 0;
+ if (action.rankLoss) {
+ loss = addOffset(action.rankLoss * rewardMultiplier, 10);
+ this.changeRank(player, -1 * loss);
+ }
+ if (action.hpLoss) {
+ damage = action.hpLoss * difficultyMultiplier;
+ damage = Math.ceil(addOffset(damage, 10));
+ this.hpLost += damage;
+ const cost = calculateHospitalizationCost(player, damage);
+ if (player.takeDamage(damage)) {
+ ++this.numHosp;
+ this.moneyLost += cost;
+ }
+ }
+ let logLossText = "";
+ if (loss > 0) {logLossText += "Lost " + formatNumber(loss, 3) + " rank. ";}
+ if (damage > 0) {logLossText += "Took " + formatNumber(damage, 0) + " damage.";}
+ if (isOperation && this.logging.ops) {
+ this.log(action.name + " failed! " + logLossText);
+ } else if (!isOperation && this.logging.contracts) {
+ this.log(action.name + " contract failed! " + logLossText);
+ }
+ isOperation ? this.completeOperation(false) : this.completeContract(false);
+ }
+ if (action.autoLevel) {action.level = action.maxLevel;} // Autolevel
+ this.startAction(player, this.action); // Repeat action
+ } catch(e) {
+ exceptionAlert(e);
+ }
+ break;
+ }
+ case ActionTypes["BlackOp"]:
+ case ActionTypes["BlackOperation"]: {
+ try {
+ const action = this.getActionObject(this.action);
+ if (action == null || !(action instanceof BlackOperation)) {
+ throw new Error("Failed to get BlackOperation Object for: " + this.action.name);
+ }
+ const difficulty = action.getDifficulty();
+ const difficultyMultiplier = Math.pow(difficulty, BladeburnerConstants.DiffMultExponentialFactor) + difficulty / BladeburnerConstants.DiffMultLinearFactor;
+
+ // Stamina loss is based on difficulty
+ this.stamina -= (BladeburnerConstants.BaseStaminaLoss * difficultyMultiplier);
+ if (this.stamina < 0) {this.stamina = 0;}
+
+ // Team loss variables
+ const teamCount = action.teamCount;
+ let teamLossMax;
+
+ if (action.attempt(this)) {
+ this.gainActionStats(player, action, true);
+ action.count = 0;
+ this.blackops[action.name] = true;
+ let rankGain = 0;
+ if (action.rankGain) {
+ rankGain = addOffset(action.rankGain * BitNodeMultipliers.BladeburnerRank, 10);
+ this.changeRank(player, rankGain);
+ }
+ teamLossMax = Math.ceil(teamCount/2);
+
+ // Operation Daedalus
+ if (action.name === "Operation Daedalus") {
+ this.resetAction();
+ return hackWorldDaemon(player.bitNodeN);
+ }
+
+ if (this.logging.blackops) {
+ this.log(action.name + " successful! Gained " + formatNumber(rankGain, 1) + " rank");
+ }
+ } else {
+ this.gainActionStats(player, action, false);
+ let rankLoss = 0;
+ let damage = 0;
+ if (action.rankLoss) {
+ rankLoss = addOffset(action.rankLoss, 10);
+ this.changeRank(player, -1 * rankLoss);
+ }
+ if (action.hpLoss) {
+ damage = action.hpLoss * difficultyMultiplier;
+ damage = Math.ceil(addOffset(damage, 10));
+ const cost = calculateHospitalizationCost(player, damage);
+ if (player.takeDamage(damage)) {
+ ++this.numHosp;
+ this.moneyLost += cost;
+ }
+ }
+ teamLossMax = Math.floor(teamCount);
+
+ if (this.logging.blackops) {
+ this.log(action.name + " failed! Lost " + formatNumber(rankLoss, 1) + " rank and took " + formatNumber(damage, 0) + " damage");
+ }
+ }
+
+ this.resetAction(); // Stop regardless of success or fail
+
+ // Calculate team lossses
+ if (teamCount >= 1) {
+ const losses = getRandomInt(1, teamLossMax);
+ this.teamSize -= losses;
+ this.teamLost += losses;
+ if (this.logging.blackops) {
+ this.log("You lost " + formatNumber(losses, 0) + " team members during " + action.name);
+ }
+ }
+ } catch(e) {
+ exceptionAlert(e);
+ }
+ break;
+ }
+ case ActionTypes["Training"]: {
+ this.stamina -= (0.5 * BladeburnerConstants.BaseStaminaLoss);
+ const strExpGain = 30 * player.strength_exp_mult,
+ defExpGain = 30 * player.defense_exp_mult,
+ dexExpGain = 30 * player.dexterity_exp_mult,
+ agiExpGain = 30 * player.agility_exp_mult,
+ staminaGain = 0.04 * this.skillMultipliers.stamina;
+ player.gainStrengthExp(strExpGain);
+ player.gainDefenseExp(defExpGain);
+ player.gainDexterityExp(dexExpGain);
+ player.gainAgilityExp(agiExpGain);
+ this.staminaBonus += (staminaGain);
+ if (this.logging.general) {
+ this.log("Training completed. Gained: " +
+ formatNumber(strExpGain, 1) + " str exp, " +
+ formatNumber(defExpGain, 1) + " def exp, " +
+ formatNumber(dexExpGain, 1) + " dex exp, " +
+ formatNumber(agiExpGain, 1) + " agi exp, " +
+ formatNumber(staminaGain, 3) + " max stamina");
+ }
+ this.startAction(player, this.action); // Repeat action
+ break;
+ }
+ case ActionTypes["FieldAnalysis"]:
+ case ActionTypes["Field Analysis"]: {
+ // Does not use stamina. Effectiveness depends on hacking, int, and cha
+ let eff = 0.04 * Math.pow(player.hacking_skill, 0.3) +
+ 0.04 * Math.pow(player.intelligence, 0.9) +
+ 0.02 * Math.pow(player.charisma, 0.3);
+ eff *= player.bladeburner_analysis_mult;
+ if (isNaN(eff) || eff < 0) {
+ throw new Error("Field Analysis Effectiveness calculated to be NaN or negative");
+ }
+ const hackingExpGain = 20 * player.hacking_exp_mult,
+ charismaExpGain = 20 * player.charisma_exp_mult;
+ player.gainHackingExp(hackingExpGain);
+ player.gainIntelligenceExp(BladeburnerConstants.BaseIntGain);
+ player.gainCharismaExp(charismaExpGain);
+ this.changeRank(player, 0.1 * BitNodeMultipliers.BladeburnerRank);
+ this.getCurrentCity().improvePopulationEstimateByPercentage(eff * this.skillMultipliers.successChanceEstimate);
+ if (this.logging.general) {
+ this.log("Field analysis completed. Gained 0.1 rank, " + formatNumber(hackingExpGain, 1) + " hacking exp, and " + formatNumber(charismaExpGain, 1) + " charisma exp");
+ }
+ this.startAction(player, this.action); // Repeat action
+ break;
+ }
+ case ActionTypes["Recruitment"]: {
+ const successChance = this.getRecruitmentSuccessChance(player);
+ if (Math.random() < successChance) {
+ const expGain = 2 * BladeburnerConstants.BaseStatGain * this.actionTimeToComplete;
+ player.gainCharismaExp(expGain);
+ ++this.teamSize;
+ if (this.logging.general) {
+ this.log("Successfully recruited a team member! Gained " + formatNumber(expGain, 1) + " charisma exp");
+ }
+ } else {
+ const expGain = BladeburnerConstants.BaseStatGain * this.actionTimeToComplete;
+ player.gainCharismaExp(expGain);
+ if (this.logging.general) {
+ this.log("Failed to recruit a team member. Gained " + formatNumber(expGain, 1) + " charisma exp");
+ }
+ }
+ this.startAction(player, this.action); // Repeat action
+ break;
+ }
+ case ActionTypes["Diplomacy"]: {
+ let eff = this.getDiplomacyEffectiveness(player);
+ this.getCurrentCity().chaos *= eff;
+ if (this.getCurrentCity().chaos < 0) { this.getCurrentCity().chaos = 0; }
+ if (this.logging.general) {
+ this.log(`Diplomacy completed. Chaos levels in the current city fell by ${numeralWrapper.formatPercentage(1 - eff)}`);
+ }
+ this.startAction(player, this.action); // Repeat Action
+ break;
+ }
+ case ActionTypes["Hyperbolic Regeneration Chamber"]: {
+ player.regenerateHp(BladeburnerConstants.HrcHpGain);
+
+ const staminaGain = this.maxStamina * (BladeburnerConstants.HrcStaminaGain / 100);
+ this.stamina = Math.min(this.maxStamina, this.stamina + staminaGain);
+ this.startAction(player, this.action);
+ if (this.logging.general) {
+ this.log(`Rested in Hyperbolic Regeneration Chamber. Restored ${BladeburnerConstants.HrcHpGain} HP and gained ${numeralWrapper.formatStamina(staminaGain)} stamina`);
+ }
+ break;
+ }
+ default:
+ console.error(`Bladeburner.completeAction() called for invalid action: ${this.action.type}`);
+ break;
+ }
+ }
+
+ changeRank(player: IPlayer, change: number): void {
+ if (isNaN(change)) {throw new Error("NaN passed into Bladeburner.changeRank()");}
+ this.rank += change;
+ if (this.rank < 0) {this.rank = 0;}
+ this.maxRank = Math.max(this.rank, this.maxRank);
+
+ const bladeburnersFactionName = "Bladeburners";
+ if (factionExists(bladeburnersFactionName)) {
+ const bladeburnerFac = Factions[bladeburnersFactionName];
+ if (!(bladeburnerFac instanceof Faction)) {
+ throw new Error("Could not properly get Bladeburner Faction object in Bladeburner UI Overview Faction button");
+ }
+ if (bladeburnerFac.isMember) {
+ const favorBonus = 1 + (bladeburnerFac.favor / 100);
+ bladeburnerFac.playerReputation += (BladeburnerConstants.RankToFactionRepFactor * change * player.faction_rep_mult * favorBonus);
+ }
+ }
+
+ // Gain skill points
+ const rankNeededForSp = (this.totalSkillPoints+1) * BladeburnerConstants.RanksPerSkillPoint;
+ if (this.maxRank >= rankNeededForSp) {
+ // Calculate how many skill points to gain
+ const gainedSkillPoints = Math.floor((this.maxRank - rankNeededForSp) / BladeburnerConstants.RanksPerSkillPoint + 1);
+ this.skillPoints += gainedSkillPoints;
+ this.totalSkillPoints += gainedSkillPoints;
+ }
+ }
+
+ processAction(player: IPlayer, seconds: number): void {
+ if (this.action.type === ActionTypes["Idle"]) return;
+ if (this.actionTimeToComplete <= 0) {
+ throw new Error(`Invalid actionTimeToComplete value: ${this.actionTimeToComplete}, type; ${this.action.type}`);
+ }
+ if (!(this.action instanceof ActionIdentifier)) {
+ throw new Error("Bladeburner.action is not an ActionIdentifier Object");
+ }
+
+ // If the previous action went past its completion time, add to the next action
+ // This is not added inmediatly in case the automation changes the action
+ this.actionTimeCurrent += seconds + this.actionTimeOverflow;
+ this.actionTimeOverflow = 0;
+ if (this.actionTimeCurrent >= this.actionTimeToComplete) {
+ this.actionTimeOverflow = this.actionTimeCurrent - this.actionTimeToComplete;
+ return this.completeAction(player);
+ }
+ }
+
+ calculateStaminaGainPerSecond(player: IPlayer): number {
+ const effAgility = player.agility * this.skillMultipliers.effAgi;
+ const maxStaminaBonus = this.maxStamina / BladeburnerConstants.MaxStaminaToGainFactor;
+ const gain = (BladeburnerConstants.StaminaGainPerSecond + maxStaminaBonus) * Math.pow(effAgility, 0.17);
+ return gain * (this.skillMultipliers.stamina * player.bladeburner_stamina_gain_mult);
+ }
+
+ calculateMaxStamina(player: IPlayer) {
+ const effAgility = player.agility * this.skillMultipliers.effAgi;
+ let maxStamina = (Math.pow(effAgility, 0.8) + this.staminaBonus) *
+ this.skillMultipliers.stamina *
+ player.bladeburner_max_stamina_mult;
+ if (this.maxStamina !== maxStamina) {
+ const oldMax = this.maxStamina;
+ this.maxStamina = maxStamina;
+ this.stamina = this.maxStamina * this.stamina / oldMax;
+ }
+ if (isNaN(maxStamina)) {throw new Error("Max Stamina calculated to be NaN in Bladeburner.calculateMaxStamina()");}
+ }
+
+ create(): void {
+ this.contracts["Tracking"] = new Contract({
+ name:"Tracking",
+ desc:"Identify and locate Synthoids. This contract involves reconnaissance " +
+ "and information-gathering ONLY. Do NOT engage. Stealth is of the utmost importance.
" +
+ "Successfully completing Tracking contracts will slightly improve your Synthoid population estimate for " +
+ "whatever city you are currently in.",
+ baseDifficulty:125,difficultyFac:1.02,rewardFac:1.041,
+ rankGain:0.3, hpLoss:0.5,
+ count:getRandomInt(25, 150), countGrowth:getRandomInt(5, 75)/10,
+ weights:{hack:0,str:0.05,def:0.05,dex:0.35,agi:0.35,cha:0.1, int:0.05},
+ decays:{hack:0,str:0.91,def:0.91,dex:0.91,agi:0.91,cha:0.9, int:1},
+ isStealth:true,
+ });
+ this.contracts["Bounty Hunter"] = new Contract({
+ name:"Bounty Hunter",
+ desc:"Hunt down and capture fugitive Synthoids. These Synthoids are wanted alive.
" +
+ "Successfully completing a Bounty Hunter contract will lower the population in your " +
+ "current city, and will also increase its chaos level.",
+ baseDifficulty:250, difficultyFac:1.04,rewardFac:1.085,
+ rankGain:0.9, hpLoss:1,
+ count:getRandomInt(5, 150), countGrowth:getRandomInt(5, 75)/10,
+ weights:{hack:0,str:0.15,def:0.15,dex:0.25,agi:0.25,cha:0.1, int:0.1},
+ decays:{hack:0,str:0.91,def:0.91,dex:0.91,agi:0.91,cha:0.8, int:0.9},
+ isKill:true,
+ });
+ this.contracts["Retirement"] = new Contract({
+ name:"Retirement",
+ desc:"Hunt down and retire (kill) rogue Synthoids.
" +
+ "Successfully completing a Retirement contract will lower the population in your current " +
+ "city, and will also increase its chaos level.",
+ baseDifficulty:200, difficultyFac:1.03, rewardFac:1.065,
+ rankGain:0.6, hpLoss:1,
+ count:getRandomInt(5, 150), countGrowth:getRandomInt(5, 75)/10,
+ weights:{hack:0,str:0.2,def:0.2,dex:0.2,agi:0.2,cha:0.1, int:0.1},
+ decays:{hack:0,str:0.91,def:0.91,dex:0.91,agi:0.91,cha:0.8, int:0.9},
+ isKill:true,
+ });
+
+ this.operations["Investigation"] = new Operation({
+ name:"Investigation",
+ desc:"As a field agent, investigate and identify Synthoid " +
+ "populations, movements, and operations.
Successful " +
+ "Investigation ops will increase the accuracy of your " +
+ "synthoid data.
" +
+ "You will NOT lose HP from failed Investigation ops.",
+ baseDifficulty:400, difficultyFac:1.03,rewardFac:1.07,reqdRank:25,
+ rankGain:2.2, rankLoss:0.2,
+ count:getRandomInt(1, 100), countGrowth:getRandomInt(10, 40)/10,
+ weights:{hack:0.25,str:0.05,def:0.05,dex:0.2,agi:0.1,cha:0.25, int:0.1},
+ decays:{hack:0.85,str:0.9,def:0.9,dex:0.9,agi:0.9,cha:0.7, int:0.9},
+ isStealth:true,
+ });
+ this.operations["Undercover Operation"] = new Operation({
+ name:"Undercover Operation",
+ desc:"Conduct undercover operations to identify hidden " +
+ "and underground Synthoid communities and organizations.
" +
+ "Successful Undercover ops will increase the accuracy of your synthoid " +
+ "data.",
+ baseDifficulty:500, difficultyFac:1.04, rewardFac:1.09, reqdRank:100,
+ rankGain:4.4, rankLoss:0.4, hpLoss:2,
+ count:getRandomInt(1, 100), countGrowth:getRandomInt(10, 40)/10,
+ weights:{hack:0.2,str:0.05,def:0.05,dex:0.2,agi:0.2,cha:0.2, int:0.1},
+ decays:{hack:0.8,str:0.9,def:0.9,dex:0.9,agi:0.9,cha:0.7, int:0.9},
+ isStealth:true,
+ });
+ this.operations["Sting Operation"] = new Operation({
+ name:"Sting Operation",
+ desc:"Conduct a sting operation to bait and capture particularly " +
+ "notorious Synthoid criminals.",
+ baseDifficulty:650, difficultyFac:1.04, rewardFac:1.095, reqdRank:500,
+ rankGain:5.5, rankLoss:0.5, hpLoss:2.5,
+ count:getRandomInt(1, 150), countGrowth:getRandomInt(3, 40)/10,
+ weights:{hack:0.25,str:0.05,def:0.05,dex:0.25,agi:0.1,cha:0.2, int:0.1},
+ decays:{hack:0.8,str:0.85,def:0.85,dex:0.85,agi:0.85,cha:0.7, int:0.9},
+ isStealth:true,
+ });
+ this.operations["Raid"] = new Operation({
+ name:"Raid",
+ desc:"Lead an assault on a known Synthoid community. Note that " +
+ "there must be an existing Synthoid community in your current city " +
+ "in order for this Operation to be successful.",
+ baseDifficulty:800, difficultyFac:1.045, rewardFac:1.1, reqdRank:3000,
+ rankGain:55,rankLoss:2.5,hpLoss:50,
+ count:getRandomInt(1, 150), countGrowth:getRandomInt(2, 40)/10,
+ weights:{hack:0.1,str:0.2,def:0.2,dex:0.2,agi:0.2,cha:0, int:0.1},
+ decays:{hack:0.7,str:0.8,def:0.8,dex:0.8,agi:0.8,cha:0, int:0.9},
+ isKill:true,
+ });
+ this.operations["Stealth Retirement Operation"] = new Operation({
+ name:"Stealth Retirement Operation",
+ desc:"Lead a covert operation to retire Synthoids. The " +
+ "objective is to complete the task without " +
+ "drawing any attention. Stealth and discretion are key.",
+ baseDifficulty:1000, difficultyFac:1.05, rewardFac:1.11, reqdRank:20e3,
+ rankGain:22, rankLoss:2, hpLoss:10,
+ count:getRandomInt(1, 150), countGrowth:getRandomInt(1, 20)/10,
+ weights:{hack:0.1,str:0.1,def:0.1,dex:0.3,agi:0.3,cha:0, int:0.1},
+ decays:{hack:0.7,str:0.8,def:0.8,dex:0.8,agi:0.8,cha:0, int:0.9},
+ isStealth:true, isKill:true,
+ });
+ this.operations["Assassination"] = new Operation({
+ name:"Assassination",
+ desc:"Assassinate Synthoids that have been identified as " +
+ "important, high-profile social and political leaders " +
+ "in the Synthoid communities.",
+ baseDifficulty:1500, difficultyFac:1.06, rewardFac:1.14, reqdRank:50e3,
+ rankGain:44, rankLoss:4, hpLoss:5,
+ count:getRandomInt(1, 150), countGrowth:getRandomInt(1, 20)/10,
+ weights:{hack:0.1,str:0.1,def:0.1,dex:0.3,agi:0.3,cha:0, int:0.1},
+ decays:{hack:0.6,str:0.8,def:0.8,dex:0.8,agi:0.8,cha:0, int:0.8},
+ isStealth:true, isKill:true,
+ });
+ }
+
+ process(player: IPlayer): void {
+ // Edge case condition...if Operation Daedalus is complete trigger the BitNode
+ if (redPillFlag === false && this.blackops.hasOwnProperty("Operation Daedalus")) {
+ return hackWorldDaemon(player.bitNodeN);
+ }
+
+ // If the Player starts doing some other actions, set action to idle and alert
+ if (Augmentations[AugmentationNames.BladesSimulacrum].owned === false && player.isWorking) {
+ if (this.action.type !== ActionTypes["Idle"]) {
+ let msg = "Your Bladeburner action was cancelled because you started doing something else.";
+ if (this.automateEnabled) {
+ msg += `
Your automation was disabled as well. You will have to re-enable it through the Bladeburner console`
+ this.automateEnabled = false;
+ }
+ if (!Settings.SuppressBladeburnerPopup) {
+ dialogBoxCreate(msg);
+ }
+ }
+ this.resetAction();
+ }
+
+ // If the Player has no Stamina, set action to idle
+ if (this.stamina <= 0) {
+ this.log("Your Bladeburner action was cancelled because your stamina hit 0");
+ this.resetAction();
+ }
+
+ // A 'tick' for this mechanic is one second (= 5 game cycles)
+ if (this.storedCycles >= BladeburnerConstants.CyclesPerSecond) {
+ let seconds = Math.floor(this.storedCycles / BladeburnerConstants.CyclesPerSecond);
+ seconds = Math.min(seconds, 5); // Max of 5 'ticks'
+ this.storedCycles -= seconds * BladeburnerConstants.CyclesPerSecond;
+
+ // Stamina
+ this.calculateMaxStamina(player);
+ this.stamina += (this.calculateStaminaGainPerSecond(player) * seconds);
+ this.stamina = Math.min(this.maxStamina, this.stamina);
+
+ // Count increase for contracts/operations
+ for (const contract of (Object.values(this.contracts) as Contract[])) {
+ contract.count += (seconds * contract.countGrowth/BladeburnerConstants.ActionCountGrowthPeriod);
+ }
+ for (const op of (Object.values(this.operations) as Operation[])) {
+ op.count += (seconds * op.countGrowth/BladeburnerConstants.ActionCountGrowthPeriod);
+ }
+
+ // Chaos goes down very slowly
+ for (const cityName of BladeburnerConstants.CityNames) {
+ const city = this.cities[cityName];
+ if (!(city instanceof City)) {throw new Error("Invalid City object when processing passive chaos reduction in Bladeburner.process");}
+ city.chaos -= (0.0001 * seconds);
+ city.chaos = Math.max(0, city.chaos);
+ }
+
+ // Random Events
+ this.randomEventCounter -= seconds;
+ if (this.randomEventCounter <= 0) {
+ this.randomEvent();
+ // Add instead of setting because we might have gone over the required time for the event
+ this.randomEventCounter += getRandomInt(240, 600);
+ }
+
+ this.processAction(player, seconds);
+
+ // Automation
+ if (this.automateEnabled) {
+ // Note: Do NOT set this.action = this.automateActionHigh/Low since it creates a reference
+ if (this.stamina <= this.automateThreshLow) {
+ if (this.action.name !== this.automateActionLow.name || this.action.type !== this.automateActionLow.type) {
+ this.action = new ActionIdentifier({type: this.automateActionLow.type, name: this.automateActionLow.name});
+ this.startAction(player, this.action);
+ }
+ } else if (this.stamina >= this.automateThreshHigh) {
+ if (this.action.name !== this.automateActionHigh.name || this.action.type !== this.automateActionHigh.type) {
+ this.action = new ActionIdentifier({type: this.automateActionHigh.type, name: this.automateActionHigh.name});
+ this.startAction(player, this.action);
+ }
+ }
+ }
+ }
}
getTypeAndNameFromActionId(actionId: IActionIdentifier): {type: string, name: string} {
- return getTypeAndNameFromActionId(this, actionId);
- }
+ const res = {type: '', name: ''};
+ const types = Object.keys(ActionTypes);
+ for (let i = 0; i < types.length; ++i) {
+ if (actionId.type === ActionTypes[types[i]]) {
+ res.type = types[i];
+ break;
+ }
+ }
+ if (res.type == null) {res.type = "Idle";}
+ res.name = actionId.name != null ? actionId.name : "Idle";
+ return res;
+ }
getContractNamesNetscriptFn(): string[] {
- return getContractNamesNetscriptFn(this);
+ return Object.keys(this.contracts);
}
-
getOperationNamesNetscriptFn(): string[] {
- return getOperationNamesNetscriptFn(this);
+ return Object.keys(this.operations);
}
-
getBlackOpNamesNetscriptFn(): string[] {
- return getBlackOpNamesNetscriptFn(this);
+ return Object.keys(BlackOperations);
}
-
getGeneralActionNamesNetscriptFn(): string[] {
- return getGeneralActionNamesNetscriptFn(this);
+ return Object.keys(GeneralActions);
}
-
getSkillNamesNetscriptFn(): string[] {
- return getSkillNamesNetscriptFn(this);
+ return Object.keys(Skills);
}
+ startActionNetscriptFn(player: IPlayer, type: string, name: string, workerScript: WorkerScript) {
+ const errorLogText = `Invalid action: type='${type}' name='${name}'`;
+ const actionId = this.getActionIdFromTypeAndName(type, name);
+ if (actionId == null) {
+ workerScript.log("bladeburner.startAction", errorLogText);
+ return false;
+ }
- startActionNetscriptFn(player: IPlayer, type: string, name: string, workerScript: WorkerScript): boolean {
- return startActionNetscriptFn(this, player, type, name, workerScript);
+ // Special logic for Black Ops
+ if (actionId.type === ActionTypes["BlackOp"]) {
+ // Can't start a BlackOp if you don't have the required rank
+ const action = this.getActionObject(actionId);
+ if(action == null) throw new Error('Action not found ${actionId.type}, ${actionId.name}');
+ if(!(action instanceof BlackOperation)) throw new Error(`Action should be BlackOperation but isn't`);
+ const blackOp = (action as BlackOperation);
+ if (action.reqdRank > this.rank) {
+ workerScript.log("bladeburner.startAction", `Insufficient rank to start Black Op '${actionId.name}'.`);
+ return false;
+ }
+
+ // Can't start a BlackOp if its already been done
+ if (this.blackops[actionId.name] != null) {
+ workerScript.log("bladeburner.startAction", `Black Op ${actionId.name} has already been completed.`);
+ return false;
+ }
+
+ // Can't start a BlackOp if you haven't done the one before it
+ var blackops = [];
+ for (const nm in BlackOperations) {
+ if (BlackOperations.hasOwnProperty(nm)) {
+ blackops.push(nm);
+ }
+ }
+ blackops.sort(function(a, b) {
+ return (BlackOperations[a].reqdRank - BlackOperations[b].reqdRank); // Sort black ops in intended order
+ });
+
+ let i = blackops.indexOf(actionId.name);
+ if (i === -1) {
+ workerScript.log("bladeburner.startAction", `Invalid Black Op: '${name}'`);
+ return false;
+ }
+
+ if (i > 0 && this.blackops[blackops[i-1]] == null) {
+ workerScript.log("bladeburner.startAction", `Preceding Black Op must be completed before starting '${actionId.name}'.`);
+ return false;
+ }
+ }
+
+ try {
+ this.startAction(player, actionId);
+ workerScript.log("bladeburner.startAction", `Starting bladeburner action with type '${type}' and name ${name}"`);
+ return true;
+ } catch(e) {
+ this.resetAction();
+ workerScript.log("bladeburner.startAction", errorLogText);
+ return false;
+ }
}
-
getActionTimeNetscriptFn(player: IPlayer, type: string, name: string, workerScript: WorkerScript): number {
- return getActionTimeNetscriptFn(this, player, type, name, workerScript);
- }
+ const errorLogText = `Invalid action: type='${type}' name='${name}'`
+ const actionId = this.getActionIdFromTypeAndName(type, name);
+ if (actionId == null) {
+ workerScript.log("bladeburner.getActionTime", errorLogText);
+ return -1;
+ }
+ const actionObj = this.getActionObject(actionId);
+ if (actionObj == null) {
+ workerScript.log("bladeburner.getActionTime", errorLogText);
+ return -1;
+ }
+
+ switch (actionId.type) {
+ case ActionTypes["Contract"]:
+ case ActionTypes["Operation"]:
+ case ActionTypes["BlackOp"]:
+ case ActionTypes["BlackOperation"]:
+ return actionObj.getActionTime(this);
+ case ActionTypes["Training"]:
+ case ActionTypes["Field Analysis"]:
+ case ActionTypes["FieldAnalysis"]:
+ return 30;
+ case ActionTypes["Recruitment"]:
+ return this.getRecruitmentTime(player);
+ case ActionTypes["Diplomacy"]:
+ case ActionTypes["Hyperbolic Regeneration Chamber"]:
+ return 60;
+ default:
+ workerScript.log("bladeburner.getActionTime", errorLogText);
+ return -1;
+ }
+ }
getActionEstimatedSuccessChanceNetscriptFn(player: IPlayer, type: string, name: string, workerScript: WorkerScript): number {
- return getActionEstimatedSuccessChanceNetscriptFn(this, player, type, name, workerScript);
- }
+ const errorLogText = `Invalid action: type='${type}' name='${name}'`
+ const actionId = this.getActionIdFromTypeAndName(type, name);
+ if (actionId == null) {
+ workerScript.log("bladeburner.getActionEstimatedSuccessChance", errorLogText);
+ return -1;
+ }
- getActionCountRemainingNetscriptFn(type: string, name: string, workerScript: WorkerScript): number {
- return getActionCountRemainingNetscriptFn(this, type, name, workerScript);
- }
+ const actionObj = this.getActionObject(actionId);
+ if (actionObj == null) {
+ workerScript.log("bladeburner.getActionEstimatedSuccessChance", errorLogText);
+ return -1;
+ }
- getSkillLevelNetscriptFn(skillName: string, workerScript: WorkerScript): number {
- return getSkillLevelNetscriptFn(this, skillName, workerScript);
+ switch (actionId.type) {
+ case ActionTypes["Contract"]:
+ case ActionTypes["Operation"]:
+ case ActionTypes["BlackOp"]:
+ case ActionTypes["BlackOperation"]:
+ return actionObj.getSuccessChance(this, {est:true});
+ case ActionTypes["Training"]:
+ case ActionTypes["Field Analysis"]:
+ case ActionTypes["FieldAnalysis"]:
+ return 1;
+ case ActionTypes["Recruitment"]:
+ return this.getRecruitmentSuccessChance(player);
+ default:
+ workerScript.log("bladeburner.getActionEstimatedSuccessChance", errorLogText);
+ return -1;
+ }
}
+ getActionCountRemainingNetscriptFn(type: string, name: string, workerScript: WorkerScript) {
+ const errorLogText = `Invalid action: type='${type}' name='${name}'`;
+ const actionId = this.getActionIdFromTypeAndName(type, name);
+ if (actionId == null) {
+ workerScript.log("bladeburner.getActionCountRemaining", errorLogText);
+ return -1;
+ }
- getSkillUpgradeCostNetscriptFn(skillName: string, workerScript: WorkerScript): number {
- return getSkillUpgradeCostNetscriptFn(this, skillName, workerScript);
+ const actionObj = this.getActionObject(actionId);
+ if (actionObj == null) {
+ workerScript.log("bladeburner.getActionCountRemaining", errorLogText);
+ return -1;
+ }
+
+ switch (actionId.type) {
+ case ActionTypes["Contract"]:
+ case ActionTypes["Operation"]:
+ return Math.floor( actionObj.count );
+ case ActionTypes["BlackOp"]:
+ case ActionTypes["BlackOperation"]:
+ if (this.blackops[name] != null) {
+ return 0;
+ } else {
+ return 1;
+ }
+ case ActionTypes["Training"]:
+ case ActionTypes["Field Analysis"]:
+ case ActionTypes["FieldAnalysis"]:
+ return Infinity;
+ default:
+ workerScript.log("bladeburner.getActionCountRemaining", errorLogText);
+ return -1;
+ }
}
+ getSkillLevelNetscriptFn(skillName: string, workerScript: WorkerScript) {
+ if (skillName === "" || !Skills.hasOwnProperty(skillName)) {
+ workerScript.log("bladeburner.getSkillLevel", `Invalid skill: '${skillName}'`);
+ return -1;
+ }
- upgradeSkillNetscriptFn(skillName: string, workerScript: WorkerScript): boolean {
- return upgradeSkillNetscriptFn(this, skillName, workerScript);
+ if (this.skills[skillName] == null) {
+ return 0;
+ } else {
+ return this.skills[skillName];
+ }
}
+ getSkillUpgradeCostNetscriptFn(skillName: string, workerScript: WorkerScript) {
+ if (skillName === "" || !Skills.hasOwnProperty(skillName)) {
+ workerScript.log("bladeburner.getSkillUpgradeCost", `Invalid skill: '${skillName}'`);
+ return -1;
+ }
+ const skill = Skills[skillName];
+ if (this.skills[skillName] == null) {
+ return skill.calculateCost(0);
+ } else {
+ return skill.calculateCost(this.skills[skillName]);
+ }
+ }
+ upgradeSkillNetscriptFn(skillName: string, workerScript: WorkerScript) {
+ const errorLogText = `Invalid skill: '${skillName}'`;
+ if (!Skills.hasOwnProperty(skillName)) {
+ workerScript.log("bladeburner.upgradeSkill", errorLogText);
+ return false;
+ }
+
+ const skill = Skills[skillName];
+ let currentLevel = 0;
+ if (this.skills[skillName] && !isNaN(this.skills[skillName])) {
+ currentLevel = this.skills[skillName];
+ }
+ const cost = skill.calculateCost(currentLevel);
+
+ if(skill.maxLvl && currentLevel >= skill.maxLvl) {
+ workerScript.log("bladeburner.upgradeSkill", `Skill '${skillName}' is already maxed.`);
+ return false;
+ }
+
+ if (this.skillPoints < cost) {
+ workerScript.log("bladeburner.upgradeSkill", `You do not have enough skill points to upgrade ${skillName} (You have ${this.skillPoints}, you need ${cost})`);
+ return false;
+ }
+
+ this.skillPoints -= cost;
+ this.upgradeSkill(skill);
+ workerScript.log("bladeburner.upgradeSkill", `'${skillName}' upgraded to level ${this.skills[skillName]}`);
+ return true;
+ }
getTeamSizeNetscriptFn(type: string, name: string, workerScript: WorkerScript): number {
- return getTeamSizeNetscriptFn(this, type, name, workerScript);
- }
+ if (type === "" && name === "") {
+ return this.teamSize;
+ }
+ const errorLogText = `Invalid action: type='${type}' name='${name}'`;
+ const actionId = this.getActionIdFromTypeAndName(type, name);
+ if (actionId == null) {
+ workerScript.log("bladeburner.getTeamSize", errorLogText);
+ return -1;
+ }
+
+ const actionObj = this.getActionObject(actionId);
+ if (actionObj == null) {
+ workerScript.log("bladeburner.getTeamSize", errorLogText);
+ return -1;
+ }
+
+ if (actionId.type === ActionTypes["Operation"] ||
+ actionId.type === ActionTypes["BlackOp"] ||
+ actionId.type === ActionTypes["BlackOperation"]) {
+ return actionObj.teamCount;
+ } else {
+ return 0;
+ }
+ }
setTeamSizeNetscriptFn(type: string, name: string, size: number, workerScript: WorkerScript): number {
- return setTeamSizeNetscriptFn(this, type, name, size, workerScript);
+ const errorLogText = `Invalid action: type='${type}' name='${name}'`;
+ const actionId = this.getActionIdFromTypeAndName(type, name);
+ if (actionId == null) {
+ workerScript.log("bladeburner.setTeamSize", errorLogText);
+ return -1;
+ }
+
+ if (actionId.type !== ActionTypes["Operation"] &&
+ actionId.type !== ActionTypes["BlackOp"] &&
+ actionId.type !== ActionTypes["BlackOperation"]) {
+ workerScript.log("bladeburner.setTeamSize", "Only valid for 'Operations' and 'BlackOps'");
+ return -1;
+ }
+
+ const actionObj = this.getActionObject(actionId);
+ if (actionObj == null) {
+ workerScript.log("bladeburner.setTeamSize", errorLogText);
+ return -1;
+ }
+
+ let sanitizedSize = Math.round(size);
+ if (isNaN(sanitizedSize) || sanitizedSize < 0) {
+ workerScript.log("bladeburner.setTeamSize", `Invalid size: ${size}`);
+ return -1;
+ }
+ if (this.teamSize < sanitizedSize) {sanitizedSize = this.teamSize;}
+ actionObj.teamCount = sanitizedSize;
+ workerScript.log("bladeburner.setTeamSize", `Team size for '${name}' set to ${sanitizedSize}.`);
+ return sanitizedSize;
+ }
+ joinBladeburnerFactionNetscriptFn(workerScript: WorkerScript): boolean {
+ var bladeburnerFac = Factions["Bladeburners"];
+ if (bladeburnerFac.isMember) {
+ return true;
+ } else if (this.rank >= BladeburnerConstants.RankNeededForFaction) {
+ joinFaction(bladeburnerFac);
+ workerScript.log("bladeburner.joinBladeburnerFaction", "Joined Bladeburners faction.");
+ return true;
+ } else {
+ workerScript.log("bladeburner.joinBladeburnerFaction", `You do not have the required rank (${this.rank}/${BladeburnerConstants.RankNeededForFaction}).`);
+ return false;
+ }
}
- joinBladeburnerFactionNetscriptFn(workerScript: WorkerScript): boolean {
- return joinBladeburnerFactionNetscriptFn(this, workerScript);
- }
/**
* Serialize the current object to a JSON save state.
@@ -230,1882 +1983,4 @@ export class Bladeburner implements IBladeburner {
}
}
-Reviver.constructors.Bladeburner = Bladeburner;
-
-export function getActionIdFromTypeAndName(bladeburner: IBladeburner, type: string = "", name: string = ""): IActionIdentifier | null {
- if (type === "" || name === "") {return null;}
- const action = new ActionIdentifier();
- const convertedType = type.toLowerCase().trim();
- const convertedName = name.toLowerCase().trim();
- switch (convertedType) {
- case "contract":
- case "contracts":
- case "contr":
- action.type = ActionTypes["Contract"];
- if (bladeburner.contracts.hasOwnProperty(name)) {
- action.name = name;
- return action;
- } else {
- return null;
- }
- break;
- case "operation":
- case "operations":
- case "op":
- case "ops":
- action.type = ActionTypes["Operation"];
- if (bladeburner.operations.hasOwnProperty(name)) {
- action.name = name;
- return action;
- } else {
- return null;
- }
- break;
- case "blackoperation":
- case "black operation":
- case "black operations":
- case "black op":
- case "black ops":
- case "blackop":
- case "blackops":
- action.type = ActionTypes["BlackOp"];
- if (BlackOperations.hasOwnProperty(name)) {
- action.name = name;
- return action;
- } else {
- return null;
- }
- break;
- case "general":
- case "general action":
- case "gen":
- break;
- default:
- return null;
- }
-
- if (convertedType.startsWith("gen")) {
- switch (convertedName) {
- case "training":
- action.type = ActionTypes["Training"];
- action.name = "Training";
- break;
- case "recruitment":
- case "recruit":
- action.type = ActionTypes["Recruitment"];
- action.name = "Recruitment";
- break;
- case "field analysis":
- case "fieldanalysis":
- action.type = ActionTypes["Field Analysis"];
- action.name = "Field Analysis";
- break;
- case "diplomacy":
- action.type = ActionTypes["Diplomacy"];
- action.name = "Diplomacy";
- break;
- case "hyperbolic regeneration chamber":
- action.type = ActionTypes["Hyperbolic Regeneration Chamber"];
- action.name = "Hyperbolic Regeneration Chamber";
- break;
- default:
- return null;
- }
- return action;
- }
-
- return null;
-}
-
-export function executeStartConsoleCommand(bladeburner: IBladeburner, player: IPlayer, args: string[]): void {
- if (args.length !== 3) {
- bladeburner.postToConsole("Invalid usage of 'start' console command: start [type] [name]");
- bladeburner.postToConsole("Use 'help start' for more info");
- return;
- }
- const name = args[2];
- switch (args[1].toLowerCase()) {
- case "general":
- case "gen":
- if (GeneralActions[name] != null) {
- bladeburner.action.type = ActionTypes[name];
- bladeburner.action.name = name;
- startAction(bladeburner, player, bladeburner.action);
- } else {
- bladeburner.postToConsole("Invalid action name specified: " + args[2]);
- }
- break;
- case "contract":
- case "contracts":
- if (bladeburner.contracts[name] != null) {
- bladeburner.action.type = ActionTypes.Contract;
- bladeburner.action.name = name;
- startAction(bladeburner, player, bladeburner.action);
- } else {
- bladeburner.postToConsole("Invalid contract name specified: " + args[2]);
- }
- break;
- case "ops":
- case "op":
- case "operations":
- case "operation":
- if (bladeburner.operations[name] != null) {
- bladeburner.action.type = ActionTypes.Operation;
- bladeburner.action.name = name;
- startAction(bladeburner,player, bladeburner.action);
- } else {
- bladeburner.postToConsole("Invalid Operation name specified: " + args[2]);
- }
- break;
- case "blackops":
- case "blackop":
- case "black operations":
- case "black operation":
- if (BlackOperations[name] != null) {
- bladeburner.action.type = ActionTypes.BlackOperation;
- bladeburner.action.name = name;
- startAction(bladeburner,player, bladeburner.action);
- } else {
- bladeburner.postToConsole("Invalid BlackOp name specified: " + args[2]);
- }
- break;
- default:
- bladeburner.postToConsole("Invalid action/event type specified: " + args[1]);
- bladeburner.postToConsole("Examples of valid action/event identifiers are: [general, contract, op, blackop]");
- break;
- }
-}
-
-export function executeSkillConsoleCommand(bladeburner: IBladeburner, args: string[]): void {
- switch (args.length) {
- case 1:
- // Display Skill Help Command
- bladeburner.postToConsole("Invalid usage of 'skill' console command: skill [action] [name]");
- bladeburner.postToConsole("Use 'help skill' for more info");
- break;
- case 2:
- if (args[1].toLowerCase() === "list") {
- // List all skills and their level
- bladeburner.postToConsole("Skills: ");
- const skillNames = Object.keys(Skills);
- for(let i = 0; i < skillNames.length; ++i) {
- let skill = Skills[skillNames[i]];
- let level = 0;
- if (bladeburner.skills[skill.name] != null) {level = bladeburner.skills[skill.name];}
- bladeburner.postToConsole(skill.name + ": Level " + formatNumber(level, 0));
- }
- bladeburner.postToConsole(" ");
- bladeburner.postToConsole("Effects: ");
- const multKeys = Object.keys(bladeburner.skillMultipliers);
- for (let i = 0; i < multKeys.length; ++i) {
- let mult = bladeburner.skillMultipliers[multKeys[i]];
- if (mult && mult !== 1) {
- mult = formatNumber(mult, 3);
- switch(multKeys[i]) {
- case "successChanceAll":
- bladeburner.postToConsole("Total Success Chance: x" + mult);
- break;
- case "successChanceStealth":
- bladeburner.postToConsole("Stealth Success Chance: x" + mult);
- break;
- case "successChanceKill":
- bladeburner.postToConsole("Retirement Success Chance: x" + mult);
- break;
- case "successChanceContract":
- bladeburner.postToConsole("Contract Success Chance: x" + mult);
- break;
- case "successChanceOperation":
- bladeburner.postToConsole("Operation Success Chance: x" + mult);
- break;
- case "successChanceEstimate":
- bladeburner.postToConsole("Synthoid Data Estimate: x" + mult);
- break;
- case "actionTime":
- bladeburner.postToConsole("Action Time: x" + mult);
- break;
- case "effHack":
- bladeburner.postToConsole("Hacking Skill: x" + mult);
- break;
- case "effStr":
- bladeburner.postToConsole("Strength: x" + mult);
- break;
- case "effDef":
- bladeburner.postToConsole("Defense: x" + mult);
- break;
- case "effDex":
- bladeburner.postToConsole("Dexterity: x" + mult);
- break;
- case "effAgi":
- bladeburner.postToConsole("Agility: x" + mult);
- break;
- case "effCha":
- bladeburner.postToConsole("Charisma: x" + mult);
- break;
- case "effInt":
- bladeburner.postToConsole("Intelligence: x" + mult);
- break;
- case "stamina":
- bladeburner.postToConsole("Stamina: x" + mult);
- break;
- default:
- console.warn(`Unrecognized SkillMult Key: ${multKeys[i]}`);
- break;
- }
- }
- }
- } else {
- bladeburner.postToConsole("Invalid usage of 'skill' console command: skill [action] [name]");
- bladeburner.postToConsole("Use 'help skill' for more info");
- }
- break;
- case 3:
- const skillName = args[2];
- const skill = Skills[skillName];
- if (skill == null || !(skill instanceof Skill)) {
- bladeburner.postToConsole("Invalid skill name (Note that it is case-sensitive): " + skillName);
- }
- if (args[1].toLowerCase() === "list") {
- let level = 0;
- if (bladeburner.skills[skill.name] !== undefined) {
- level = bladeburner.skills[skill.name];
- }
- bladeburner.postToConsole(skill.name + ": Level " + formatNumber(level));
- } else if (args[1].toLowerCase() === "level") {
- let currentLevel = 0;
- if (bladeburner.skills[skillName] && !isNaN(bladeburner.skills[skillName])) {
- currentLevel = bladeburner.skills[skillName];
- }
- const pointCost = skill.calculateCost(currentLevel);
- if (bladeburner.skillPoints >= pointCost) {
- bladeburner.skillPoints -= pointCost;
- bladeburner.upgradeSkill(skill);
- bladeburner.log(skill.name + " upgraded to Level " + bladeburner.skills[skillName]);
- } else {
- bladeburner.postToConsole("You do not have enough Skill Points to upgrade bladeburner. You need " + formatNumber(pointCost, 0));
- }
-
- } else {
- bladeburner.postToConsole("Invalid usage of 'skill' console command: skill [action] [name]");
- bladeburner.postToConsole("Use 'help skill' for more info");
- }
- break;
- default:
- bladeburner.postToConsole("Invalid usage of 'skill' console command: skill [action] [name]");
- bladeburner.postToConsole("Use 'help skill' for more info");
- break;
- }
-}
-
-
-export function executeLogConsoleCommand(bladeburner: IBladeburner, args: string[]): void {
- if (args.length < 3) {
- bladeburner.postToConsole("Invalid usage of log command: log [enable/disable] [action/event]");
- bladeburner.postToConsole("Use 'help log' for more details and examples");
- return;
- }
-
- let flag = true;
- if (args[1].toLowerCase().includes("d")) {flag = false;} // d for disable
-
- switch (args[2].toLowerCase()) {
- case "general":
- case "gen":
- bladeburner.logging.general = flag;
- bladeburner.log("Logging " + (flag ? "enabled" : "disabled") + " for general actions");
- break;
- case "contract":
- case "contracts":
- bladeburner.logging.contracts = flag;
- bladeburner.log("Logging " + (flag ? "enabled" : "disabled") + " for Contracts");
- break;
- case "ops":
- case "op":
- case "operations":
- case "operation":
- bladeburner.logging.ops = flag;
- bladeburner.log("Logging " + (flag ? "enabled" : "disabled") + " for Operations");
- break;
- case "blackops":
- case "blackop":
- case "black operations":
- case "black operation":
- bladeburner.logging.blackops = flag;
- bladeburner.log("Logging " + (flag ? "enabled" : "disabled") + " for BlackOps");
- break;
- case "event":
- case "events":
- bladeburner.logging.events = flag;
- bladeburner.log("Logging " + (flag ? "enabled" : "disabled") + " for events");
- break;
- case "all":
- bladeburner.logging.general = flag;
- bladeburner.logging.contracts = flag;
- bladeburner.logging.ops = flag;
- bladeburner.logging.blackops = flag;
- bladeburner.logging.events = flag;
- bladeburner.log("Logging " + (flag ? "enabled" : "disabled") + " for everything");
- break;
- default:
- bladeburner.postToConsole("Invalid action/event type specified: " + args[2]);
- bladeburner.postToConsole("Examples of valid action/event identifiers are: [general, contracts, ops, blackops, events]");
- break;
- }
-}
-
-export function executeHelpConsoleCommand(bladeburner: IBladeburner, args: string[]): void {
- if (args.length === 1) {
- for(const line of ConsoleHelpText.helpList){
- bladeburner.postToConsole(line);
- }
- } else {
- for (let i = 1; i < args.length; ++i) {
- if(!(args[i] in ConsoleHelpText)) continue;
- const helpText = ConsoleHelpText[args[i]];
- for(const line of helpText){
- bladeburner.postToConsole(line);
- }
- }
- }
-}
-
-export function executeAutomateConsoleCommand(bladeburner: IBladeburner, args: string[]): void {
- if (args.length !== 2 && args.length !== 4) {
- bladeburner.postToConsole("Invalid use of 'automate' command: automate [var] [val] [hi/low]. Use 'help automate' for more info");
- return;
- }
-
- // Enable/Disable
- if (args.length === 2) {
- const flag = args[1];
- if (flag.toLowerCase() === "status") {
- bladeburner.postToConsole("Automation: " + (bladeburner.automateEnabled ? "enabled" : "disabled"));
- if (bladeburner.automateEnabled) {
- bladeburner.postToConsole("When your stamina drops to " + formatNumber(bladeburner.automateThreshLow, 0) +
- ", you will automatically switch to " + bladeburner.automateActionLow.name +
- ". When your stamina recovers to " +
- formatNumber(bladeburner.automateThreshHigh, 0) + ", you will automatically " +
- "switch to " + bladeburner.automateActionHigh.name + ".");
- }
-
- } else if (flag.toLowerCase().includes("en")) {
- if (!(bladeburner.automateActionLow instanceof ActionIdentifier) ||
- !(bladeburner.automateActionHigh instanceof ActionIdentifier)) {
- return bladeburner.log("Failed to enable automation. Actions were not set");
- }
- bladeburner.automateEnabled = true;
- bladeburner.log("Bladeburner automation enabled");
- } else if (flag.toLowerCase().includes("d")) {
- bladeburner.automateEnabled = false;
- bladeburner.log("Bladeburner automation disabled");
- } else {
- bladeburner.log("Invalid argument for 'automate' console command: " + args[1]);
- }
- return;
- }
-
- // Set variables
- if (args.length === 4) {
- const variable = args[1];
- const val = args[2];
-
- let highLow = false; // True for high, false for low
- if (args[3].toLowerCase().includes("hi")) {highLow = true;}
-
- switch (variable) {
- case "general":
- case "gen":
- if (GeneralActions[val] != null) {
- const action = new ActionIdentifier({
- type:ActionTypes[val], name:val,
- });
- if (highLow) {
- bladeburner.automateActionHigh = action;
- } else {
- bladeburner.automateActionLow = action;
- }
- bladeburner.log("Automate (" + (highLow ? "HIGH" : "LOW") + ") action set to " + val);
- } else {
- bladeburner.postToConsole("Invalid action name specified: " + val);
- }
- break;
- case "contract":
- case "contracts":
- if (bladeburner.contracts[val] != null) {
- const action = new ActionIdentifier({
- type:ActionTypes.Contract, name:val,
- });
- if (highLow) {
- bladeburner.automateActionHigh = action;
- } else {
- bladeburner.automateActionLow = action;
- }
- bladeburner.log("Automate (" + (highLow ? "HIGH" : "LOW") + ") action set to " + val);
- } else {
- bladeburner.postToConsole("Invalid contract name specified: " + val);
- }
- break;
- case "ops":
- case "op":
- case "operations":
- case "operation":
- if (bladeburner.operations[val] != null) {
- const action = new ActionIdentifier({
- type:ActionTypes.Operation, name:val,
- });
- if (highLow) {
- bladeburner.automateActionHigh = action;
- } else {
- bladeburner.automateActionLow = action;
- }
- bladeburner.log("Automate (" + (highLow ? "HIGH" : "LOW") + ") action set to " + val);
- } else {
- bladeburner.postToConsole("Invalid Operation name specified: " + val);
- }
- break;
- case "stamina":
- if (isNaN(parseFloat(val))) {
- bladeburner.postToConsole("Invalid value specified for stamina threshold (must be numeric): " + val);
- } else {
- if (highLow) {
- bladeburner.automateThreshHigh = Number(val);
- } else {
- bladeburner.automateThreshLow = Number(val);
- }
- bladeburner.log("Automate (" + (highLow ? "HIGH" : "LOW") + ") stamina threshold set to " + val);
- }
- break;
- default:
- break;
- }
-
- return;
- }
-}
-
-export function parseCommandArguments(command: string): string[] {
- /**
- * Returns an array with command and its arguments in each index.
- * e.g. skill "blade's intuition" foo returns [skill, blade's intuition, foo]
- * The input to the fn will be trimmed and will have all whitespace replaced w/ a single space
- */
- const args = [];
- let start = 0;
- let i = 0;
- while (i < command.length) {
- const c = command.charAt(i);
- if (c === '"') { // Double quotes
- const endQuote = command.indexOf('"', i+1);
- if (endQuote !== -1 && (endQuote === command.length-1 || command.charAt(endQuote+1) === " ")) {
- args.push(command.substr(i+1, (endQuote - i - 1)));
- if (endQuote === command.length-1) {
- start = i = endQuote+1;
- } else {
- start = i = endQuote+2; // Skip the space
- }
- continue;
- }
- } else if (c === "'") { // Single quotes, same thing as above
- const endQuote = command.indexOf("'", i+1);
- if (endQuote !== -1 && (endQuote === command.length-1 || command.charAt(endQuote+1) === " ")) {
- args.push(command.substr(i+1, (endQuote - i - 1)));
- if (endQuote === command.length-1) {
- start = i = endQuote+1;
- } else {
- start = i = endQuote+2; // Skip the space
- }
- continue;
- }
- } else if (c === " ") {
- args.push(command.substr(start, i-start));
- start = i+1;
- }
- ++i;
- }
- if (start !== i) {args.push(command.substr(start, i-start));}
- return args;
-}
-
-export function executeConsoleCommand(bladeburner: IBladeburner, player: IPlayer, command: string) {
- command = command.trim();
- command = command.replace(/\s\s+/g, ' '); // Replace all whitespace w/ a single space
-
- const args = parseCommandArguments(command);
- if (args.length <= 0) return; // Log an error?
-
- switch(args[0].toLowerCase()) {
- case "automate":
- executeAutomateConsoleCommand(bladeburner, args);
- break;
- case "clear":
- case "cls":
- clearConsole(bladeburner);
- break;
- case "help":
- executeHelpConsoleCommand(bladeburner, args);
- break;
- case "log":
- executeLogConsoleCommand(bladeburner, args);
- break;
- case "skill":
- executeSkillConsoleCommand(bladeburner, args);
- break;
- case "start":
- executeStartConsoleCommand(bladeburner, player, args);
- break;
- case "stop":
- resetAction(bladeburner);
- break;
- default:
- bladeburner.postToConsole("Invalid console command");
- break;
- }
-}
-
-// Handles a potential series of commands (comm1; comm2; comm3;)
-export function executeConsoleCommands(bladeburner: IBladeburner, player: IPlayer, commands: string): void {
- try {
- // Console History
- if (bladeburner.consoleHistory[bladeburner.consoleHistory.length-1] != commands) {
- bladeburner.consoleHistory.push(commands);
- if (bladeburner.consoleHistory.length > 50) {
- bladeburner.consoleHistory.splice(0, 1);
- }
- }
-
- const arrayOfCommands = commands.split(";");
- for (let i = 0; i < arrayOfCommands.length; ++i) {
- executeConsoleCommand(bladeburner, player, arrayOfCommands[i]);
- }
- } catch(e) {
- exceptionAlert(e);
- }
-}
-
-export function clearConsole(bladeburner: IBladeburner): void {
- bladeburner.consoleLogs.length = 0;
-}
-
-export function triggerMigration(bladeburner: IBladeburner, sourceCityName: string): void {
- let destCityName = BladeburnerConstants.CityNames[getRandomInt(0, 5)];
- while (destCityName === sourceCityName) {
- destCityName = BladeburnerConstants.CityNames[getRandomInt(0, 5)];
- }
- const destCity = bladeburner.cities[destCityName];
- const sourceCity = bladeburner.cities[sourceCityName];
- if (destCity == null || sourceCity == null) {
- throw new Error("Failed to find City with name: " + destCityName);
- }
- const rand = Math.random();
- let percentage = getRandomInt(3, 15) / 100;
-
- if (rand < 0.05 && sourceCity.comms > 0) { // 5% chance for community migration
- percentage *= getRandomInt(2, 4); // Migration increases population change
- --sourceCity.comms;
- ++destCity.comms;
- }
- const count = Math.round(sourceCity.pop * percentage);
- sourceCity.pop -= count;
- destCity.pop += count;
-}
-
-export function triggerPotentialMigration(bladeburner: IBladeburner, sourceCityName: string, chance: number): void {
- if (chance == null || isNaN(chance)) {
- console.error("Invalid 'chance' parameter passed into Bladeburner.triggerPotentialMigration()");
- }
- if (chance > 1) {chance /= 100;}
- if (Math.random() < chance) {triggerMigration(bladeburner, sourceCityName);}
-}
-
-export function randomEvent(bladeburner: IBladeburner): void {
- const chance = Math.random();
-
- // Choose random source/destination city for events
- const sourceCityName = BladeburnerConstants.CityNames[getRandomInt(0, 5)];
- const sourceCity = bladeburner.cities[sourceCityName];
- if (!(sourceCity instanceof City)) {
- throw new Error("sourceCity was not a City object in Bladeburner.randomEvent()");
- }
-
- let destCityName = BladeburnerConstants.CityNames[getRandomInt(0, 5)];
- while (destCityName === sourceCityName) {
- destCityName = BladeburnerConstants.CityNames[getRandomInt(0, 5)];
- }
- const destCity = bladeburner.cities[destCityName];
-
- if (!(sourceCity instanceof City) || !(destCity instanceof City)) {
- throw new Error("sourceCity/destCity was not a City object in Bladeburner.randomEvent()");
- }
-
- if (chance <= 0.05) {
- // New Synthoid Community, 5%
- ++sourceCity.comms;
- const percentage = getRandomInt(10, 20) / 100;
- const count = Math.round(sourceCity.pop * percentage);
- sourceCity.pop += count;
- if (bladeburner.logging.events) {
- bladeburner.log("Intelligence indicates that a new Synthoid community was formed in a city");
- }
- } else if (chance <= 0.1) {
- // Synthoid Community Migration, 5%
- if (sourceCity.comms <= 0) {
- // If no comms in source city, then instead trigger a new Synthoid community event
- ++sourceCity.comms;
- const percentage = getRandomInt(10, 20) / 100;
- const count = Math.round(sourceCity.pop * percentage);
- sourceCity.pop += count;
- if (bladeburner.logging.events) {
- bladeburner.log("Intelligence indicates that a new Synthoid community was formed in a city");
- }
- } else {
- --sourceCity.comms;
- ++destCity.comms;
-
- // Change pop
- const percentage = getRandomInt(10, 20) / 100;
- const count = Math.round(sourceCity.pop * percentage);
- sourceCity.pop -= count;
- destCity.pop += count;
-
- if (bladeburner.logging.events) {
- bladeburner.log("Intelligence indicates that a Synthoid community migrated from " + sourceCityName + " to some other city");
- }
- }
- } else if (chance <= 0.3) {
- // New Synthoids (non community), 20%
- const percentage = getRandomInt(8, 24) / 100;
- const count = Math.round(sourceCity.pop * percentage);
- sourceCity.pop += count;
- if (bladeburner.logging.events) {
- bladeburner.log("Intelligence indicates that the Synthoid population of " + sourceCityName + " just changed significantly");
- }
- } else if (chance <= 0.5) {
- // Synthoid migration (non community) 20%
- triggerMigration(bladeburner, sourceCityName);
- if (bladeburner.logging.events) {
- bladeburner.log("Intelligence indicates that a large number of Synthoids migrated from " + sourceCityName + " to some other city");
- }
- } else if (chance <= 0.7) {
- // Synthoid Riots (+chaos), 20%
- sourceCity.chaos += 1;
- sourceCity.chaos *= (1 + getRandomInt(5, 20) / 100);
- if (bladeburner.logging.events) {
- bladeburner.log("Tensions between Synthoids and humans lead to riots in " + sourceCityName + "! Chaos increased");
- }
- } else if (chance <= 0.9) {
- // Less Synthoids, 20%
- const percentage = getRandomInt(8, 20) / 100;
- const count = Math.round(sourceCity.pop * percentage);
- sourceCity.pop -= count;
- if (bladeburner.logging.events) {
- bladeburner.log("Intelligence indicates that the Synthoid population of " + sourceCityName + " just changed significantly");
- }
- }
- // 10% chance of nothing happening
-}
-
-
-/**
- * Process stat gains from Contracts, Operations, and Black Operations
- * @param action(Action obj) - Derived action class
- * @param success(bool) - Whether action was successful
- */
-export function gainActionStats(bladeburner: IBladeburner, player: IPlayer, action: IAction, success: boolean): void {
- const difficulty = action.getDifficulty();
-
- /**
- * Gain multiplier based on difficulty. If it changes then the
- * same variable calculated in completeAction() needs to change too
- */
- const difficultyMult = Math.pow(difficulty, BladeburnerConstants.DiffMultExponentialFactor) + difficulty / BladeburnerConstants.DiffMultLinearFactor;
-
- const time = bladeburner.actionTimeToComplete;
- const successMult = success ? 1 : 0.5;
-
- const unweightedGain = time * BladeburnerConstants.BaseStatGain * successMult * difficultyMult;
- const unweightedIntGain = time * BladeburnerConstants.BaseIntGain * successMult * difficultyMult;
- const skillMult = bladeburner.skillMultipliers.expGain;
- player.gainHackingExp(unweightedGain * action.weights.hack * player.hacking_exp_mult * skillMult);
- player.gainStrengthExp(unweightedGain * action.weights.str * player.strength_exp_mult * skillMult);
- player.gainDefenseExp(unweightedGain * action.weights.def * player.defense_exp_mult * skillMult);
- player.gainDexterityExp(unweightedGain * action.weights.dex * player.dexterity_exp_mult * skillMult);
- player.gainAgilityExp(unweightedGain * action.weights.agi * player.agility_exp_mult * skillMult);
- player.gainCharismaExp(unweightedGain * action.weights.cha * player.charisma_exp_mult * skillMult);
- let intExp = unweightedIntGain * action.weights.int * skillMult;
- if (intExp > 1) {
- intExp = Math.pow(intExp, 0.8);
- }
- player.gainIntelligenceExp(intExp);
-}
-
-export function getDiplomacyEffectiveness(bladeburner: IBladeburner, player: IPlayer): number {
- // Returns a decimal by which the city's chaos level should be multiplied (e.g. 0.98)
- const CharismaLinearFactor = 1e3;
- const CharismaExponentialFactor = 0.045;
-
- const charismaEff = Math.pow(player.charisma, CharismaExponentialFactor) + player.charisma / CharismaLinearFactor;
- return (100 - charismaEff) / 100;
-}
-
-export function getRecruitmentSuccessChance(bladeburner: IBladeburner, player: IPlayer): number {
- return Math.pow(player.charisma, 0.45) / (bladeburner.teamSize + 1);
-}
-
-export function getRecruitmentTime(bladeburner: IBladeburner, player: IPlayer): number {
- const effCharisma = player.charisma * bladeburner.skillMultipliers.effCha;
- const charismaFactor = Math.pow(effCharisma, 0.81) + effCharisma / 90;
- return Math.max(10, Math.round(BladeburnerConstants.BaseRecruitmentTimeNeeded - charismaFactor));
-}
-
-export function resetSkillMultipliers(bladeburner: IBladeburner): void {
- bladeburner.skillMultipliers = {
- successChanceAll: 1,
- successChanceStealth: 1,
- successChanceKill: 1,
- successChanceContract: 1,
- successChanceOperation: 1,
- successChanceEstimate: 1,
- actionTime: 1,
- effHack: 1,
- effStr: 1,
- effDef: 1,
- effDex: 1,
- effAgi: 1,
- effCha: 1,
- effInt: 1,
- stamina: 1,
- money: 1,
- expGain: 1,
- };
-}
-
-export function updateSkillMultipliers(bladeburner: IBladeburner): void {
- resetSkillMultipliers(bladeburner);
- for (const skillName in bladeburner.skills) {
- if (bladeburner.skills.hasOwnProperty(skillName)) {
- const skill = Skills[skillName];
- if (skill == null) {
- throw new Error("Could not find Skill Object for: " + skillName);
- }
- const level = bladeburner.skills[skillName];
- if (level == null || level <= 0) {continue;} //Not upgraded
-
- const multiplierNames = Object.keys(bladeburner.skillMultipliers);
- for (let i = 0; i < multiplierNames.length; ++i) {
- const multiplierName = multiplierNames[i];
- if (skill.getMultiplier(multiplierName) != null && !isNaN(skill.getMultiplier(multiplierName))) {
- const value = skill.getMultiplier(multiplierName) * level;
- let multiplierValue = 1 + (value / 100);
- if (multiplierName === "actionTime") {
- multiplierValue = 1 - (value / 100);
- }
- bladeburner.skillMultipliers[multiplierName] *= multiplierValue;
- }
- }
- }
- }
-}
-
-
-// Sets the player to the "IDLE" action
-export function resetAction(bladeburner: IBladeburner): void {
- bladeburner.action = new ActionIdentifier({type:ActionTypes.Idle});
-}
-
-export function completeOperation(bladeburner: IBladeburner, success: boolean): void {
- if (bladeburner.action.type !== ActionTypes.Operation) {
- throw new Error("completeOperation() called even though current action is not an Operation");
- }
- const action = getActionObject(bladeburner, bladeburner.action);
- if (action == null) {
- throw new Error("Failed to get Contract/Operation Object for: " + bladeburner.action.name);
- }
-
- // Calculate team losses
- const teamCount = action.teamCount;
- if (teamCount >= 1) {
- let max;
- if (success) {
- max = Math.ceil(teamCount/2);
- } else {
- max = Math.floor(teamCount)
- }
- const losses = getRandomInt(0, max);
- bladeburner.teamSize -= losses;
- bladeburner.teamLost += losses;
- if (bladeburner.logging.ops && losses > 0) {
- bladeburner.log("Lost " + formatNumber(losses, 0) + " team members during this " + action.name);
- }
- }
-
- const city = bladeburner.getCurrentCity();
- switch (action.name) {
- case "Investigation":
- if (success) {
- city.improvePopulationEstimateByPercentage(0.4 * bladeburner.skillMultipliers.successChanceEstimate);
- if (Math.random() < (0.02 * bladeburner.skillMultipliers.successChanceEstimate)) {
- city.improveCommunityEstimate(1);
- }
- } else {
- triggerPotentialMigration(bladeburner, bladeburner.city, 0.1);
- }
- break;
- case "Undercover Operation":
- if (success) {
- city.improvePopulationEstimateByPercentage(0.8 * bladeburner.skillMultipliers.successChanceEstimate);
- if (Math.random() < (0.02 * bladeburner.skillMultipliers.successChanceEstimate)) {
- city.improveCommunityEstimate(1);
- }
- } else {
- triggerPotentialMigration(bladeburner, bladeburner.city, 0.15);
- }
- break;
- case "Sting Operation":
- if (success) {
- city.changePopulationByPercentage(-0.1, {changeEstEqually:true, nonZero:true});
- }
- city.changeChaosByCount(0.1);
- break;
- case "Raid":
- if (success) {
- city.changePopulationByPercentage(-1, {changeEstEqually:true, nonZero:true});
- --city.comms;
- --city.commsEst;
- } else {
- const change = getRandomInt(-10, -5) / 10;
- city.changePopulationByPercentage(change, {nonZero:true, changeEstEqually:false});
- }
- city.changeChaosByPercentage(getRandomInt(1, 5));
- break;
- case "Stealth Retirement Operation":
- if (success) {
- city.changePopulationByPercentage(-0.5, {changeEstEqually:true,nonZero:true});
- }
- city.changeChaosByPercentage(getRandomInt(-3, -1));
- break;
- case "Assassination":
- if (success) {
- city.changePopulationByCount(-1, {estChange:-1, estOffset: 0});
- }
- city.changeChaosByPercentage(getRandomInt(-5, 5));
- break;
- default:
- throw new Error("Invalid Action name in completeOperation: " + bladeburner.action.name);
- }
-}
-
-export function getActionObject(bladeburner: IBladeburner, actionId: IActionIdentifier): IAction | null {
- /**
- * Given an ActionIdentifier object, returns the corresponding
- * GeneralAction, Contract, Operation, or BlackOperation object
- */
- switch (actionId.type) {
- case ActionTypes["Contract"]:
- return bladeburner.contracts[actionId.name];
- case ActionTypes["Operation"]:
- return bladeburner.operations[actionId.name];
- case ActionTypes["BlackOp"]:
- case ActionTypes["BlackOperation"]:
- return BlackOperations[actionId.name];
- case ActionTypes["Training"]:
- return GeneralActions["Training"];
- case ActionTypes["Field Analysis"]:
- return GeneralActions["Field Analysis"];
- case ActionTypes["Recruitment"]:
- return GeneralActions["Recruitment"];
- case ActionTypes["Diplomacy"]:
- return GeneralActions["Diplomacy"];
- case ActionTypes["Hyperbolic Regeneration Chamber"]:
- return GeneralActions["Hyperbolic Regeneration Chamber"];
- default:
- return null;
- }
-}
-
-export function completeContract(bladeburner: IBladeburner, success: boolean): void {
- if (bladeburner.action.type !== ActionTypes.Contract) {
- throw new Error("completeContract() called even though current action is not a Contract");
- }
- var city = bladeburner.getCurrentCity();
- if (success) {
- switch (bladeburner.action.name) {
- case "Tracking":
- // Increase estimate accuracy by a relatively small amount
- city.improvePopulationEstimateByCount(getRandomInt(100, 1e3));
- break;
- case "Bounty Hunter":
- city.changePopulationByCount(-1, {estChange:-1, estOffset: 0});
- city.changeChaosByCount(0.02);
- break;
- case "Retirement":
- city.changePopulationByCount(-1, {estChange:-1, estOffset: 0});
- city.changeChaosByCount(0.04);
- break;
- default:
- throw new Error("Invalid Action name in completeContract: " + bladeburner.action.name);
- }
- }
-}
-
-export function completeAction(bladeburner: IBladeburner, player: IPlayer): void {
- switch (bladeburner.action.type) {
- case ActionTypes["Contract"]:
- case ActionTypes["Operation"]: {
- try {
- const isOperation = (bladeburner.action.type === ActionTypes["Operation"]);
- const action = getActionObject(bladeburner, bladeburner.action);
- if (action == null) {
- throw new Error("Failed to get Contract/Operation Object for: " + bladeburner.action.name);
- }
- const difficulty = action.getDifficulty();
- const difficultyMultiplier = Math.pow(difficulty, BladeburnerConstants.DiffMultExponentialFactor) + difficulty / BladeburnerConstants.DiffMultLinearFactor;
- const rewardMultiplier = Math.pow(action.rewardFac, action.level-1);
-
- // Stamina loss is based on difficulty
- bladeburner.stamina -= (BladeburnerConstants.BaseStaminaLoss * difficultyMultiplier);
- if (bladeburner.stamina < 0) {bladeburner.stamina = 0;}
-
- // Process Contract/Operation success/failure
- if (action.attempt(bladeburner)) {
- gainActionStats(bladeburner, player, action, true);
- ++action.successes;
- --action.count;
-
- // Earn money for contracts
- let moneyGain = 0;
- if (!isOperation) {
- moneyGain = BladeburnerConstants.ContractBaseMoneyGain * rewardMultiplier * bladeburner.skillMultipliers.money;
- player.gainMoney(moneyGain);
- player.recordMoneySource(moneyGain, "bladeburner");
- }
-
- if (isOperation) {
- action.setMaxLevel(BladeburnerConstants.OperationSuccessesPerLevel);
- } else {
- action.setMaxLevel(BladeburnerConstants.ContractSuccessesPerLevel);
- }
- if (action.rankGain) {
- const gain = addOffset(action.rankGain * rewardMultiplier * BitNodeMultipliers.BladeburnerRank, 10);
- changeRank(bladeburner, player, gain);
- if (isOperation && bladeburner.logging.ops) {
- bladeburner.log(action.name + " successfully completed! Gained " + formatNumber(gain, 3) + " rank");
- } else if (!isOperation && bladeburner.logging.contracts) {
- bladeburner.log(action.name + " contract successfully completed! Gained " + formatNumber(gain, 3) + " rank and " + numeralWrapper.formatMoney(moneyGain));
- }
- }
- isOperation ? completeOperation(bladeburner, true) : completeContract(bladeburner, true);
- } else {
- gainActionStats(bladeburner, player, action, false);
- ++action.failures;
- let loss = 0, damage = 0;
- if (action.rankLoss) {
- loss = addOffset(action.rankLoss * rewardMultiplier, 10);
- changeRank(bladeburner, player, -1 * loss);
- }
- if (action.hpLoss) {
- damage = action.hpLoss * difficultyMultiplier;
- damage = Math.ceil(addOffset(damage, 10));
- bladeburner.hpLost += damage;
- const cost = calculateHospitalizationCost(player, damage);
- if (player.takeDamage(damage)) {
- ++bladeburner.numHosp;
- bladeburner.moneyLost += cost;
- }
- }
- let logLossText = "";
- if (loss > 0) {logLossText += "Lost " + formatNumber(loss, 3) + " rank. ";}
- if (damage > 0) {logLossText += "Took " + formatNumber(damage, 0) + " damage.";}
- if (isOperation && bladeburner.logging.ops) {
- bladeburner.log(action.name + " failed! " + logLossText);
- } else if (!isOperation && bladeburner.logging.contracts) {
- bladeburner.log(action.name + " contract failed! " + logLossText);
- }
- isOperation ? completeOperation(bladeburner, false) : completeContract(bladeburner, false);
- }
- if (action.autoLevel) {action.level = action.maxLevel;} // Autolevel
- startAction(bladeburner,player, bladeburner.action); // Repeat action
- } catch(e) {
- exceptionAlert(e);
- }
- break;
- }
- case ActionTypes["BlackOp"]:
- case ActionTypes["BlackOperation"]: {
- try {
- const action = getActionObject(bladeburner, bladeburner.action);
- if (action == null || !(action instanceof BlackOperation)) {
- throw new Error("Failed to get BlackOperation Object for: " + bladeburner.action.name);
- }
- const difficulty = action.getDifficulty();
- const difficultyMultiplier = Math.pow(difficulty, BladeburnerConstants.DiffMultExponentialFactor) + difficulty / BladeburnerConstants.DiffMultLinearFactor;
-
- // Stamina loss is based on difficulty
- bladeburner.stamina -= (BladeburnerConstants.BaseStaminaLoss * difficultyMultiplier);
- if (bladeburner.stamina < 0) {bladeburner.stamina = 0;}
-
- // Team loss variables
- const teamCount = action.teamCount;
- let teamLossMax;
-
- if (action.attempt(bladeburner)) {
- gainActionStats(bladeburner, player, action, true);
- action.count = 0;
- bladeburner.blackops[action.name] = true;
- let rankGain = 0;
- if (action.rankGain) {
- rankGain = addOffset(action.rankGain * BitNodeMultipliers.BladeburnerRank, 10);
- changeRank(bladeburner, player, rankGain);
- }
- teamLossMax = Math.ceil(teamCount/2);
-
- // Operation Daedalus
- if (action.name === "Operation Daedalus") {
- resetAction(bladeburner);
- return hackWorldDaemon(player.bitNodeN);
- }
-
- if (bladeburner.logging.blackops) {
- bladeburner.log(action.name + " successful! Gained " + formatNumber(rankGain, 1) + " rank");
- }
- } else {
- gainActionStats(bladeburner, player, action, false);
- let rankLoss = 0;
- let damage = 0;
- if (action.rankLoss) {
- rankLoss = addOffset(action.rankLoss, 10);
- changeRank(bladeburner, player, -1 * rankLoss);
- }
- if (action.hpLoss) {
- damage = action.hpLoss * difficultyMultiplier;
- damage = Math.ceil(addOffset(damage, 10));
- const cost = calculateHospitalizationCost(player, damage);
- if (player.takeDamage(damage)) {
- ++bladeburner.numHosp;
- bladeburner.moneyLost += cost;
- }
- }
- teamLossMax = Math.floor(teamCount);
-
- if (bladeburner.logging.blackops) {
- bladeburner.log(action.name + " failed! Lost " + formatNumber(rankLoss, 1) + " rank and took " + formatNumber(damage, 0) + " damage");
- }
- }
-
- resetAction(bladeburner); // Stop regardless of success or fail
-
- // Calculate team lossses
- if (teamCount >= 1) {
- const losses = getRandomInt(1, teamLossMax);
- bladeburner.teamSize -= losses;
- bladeburner.teamLost += losses;
- if (bladeburner.logging.blackops) {
- bladeburner.log("You lost " + formatNumber(losses, 0) + " team members during " + action.name);
- }
- }
- } catch(e) {
- exceptionAlert(e);
- }
- break;
- }
- case ActionTypes["Training"]: {
- bladeburner.stamina -= (0.5 * BladeburnerConstants.BaseStaminaLoss);
- const strExpGain = 30 * player.strength_exp_mult,
- defExpGain = 30 * player.defense_exp_mult,
- dexExpGain = 30 * player.dexterity_exp_mult,
- agiExpGain = 30 * player.agility_exp_mult,
- staminaGain = 0.04 * bladeburner.skillMultipliers.stamina;
- player.gainStrengthExp(strExpGain);
- player.gainDefenseExp(defExpGain);
- player.gainDexterityExp(dexExpGain);
- player.gainAgilityExp(agiExpGain);
- bladeburner.staminaBonus += (staminaGain);
- if (bladeburner.logging.general) {
- bladeburner.log("Training completed. Gained: " +
- formatNumber(strExpGain, 1) + " str exp, " +
- formatNumber(defExpGain, 1) + " def exp, " +
- formatNumber(dexExpGain, 1) + " dex exp, " +
- formatNumber(agiExpGain, 1) + " agi exp, " +
- formatNumber(staminaGain, 3) + " max stamina");
- }
- startAction(bladeburner,player, bladeburner.action); // Repeat action
- break;
- }
- case ActionTypes["FieldAnalysis"]:
- case ActionTypes["Field Analysis"]: {
- // Does not use stamina. Effectiveness depends on hacking, int, and cha
- let eff = 0.04 * Math.pow(player.hacking_skill, 0.3) +
- 0.04 * Math.pow(player.intelligence, 0.9) +
- 0.02 * Math.pow(player.charisma, 0.3);
- eff *= player.bladeburner_analysis_mult;
- if (isNaN(eff) || eff < 0) {
- throw new Error("Field Analysis Effectiveness calculated to be NaN or negative");
- }
- const hackingExpGain = 20 * player.hacking_exp_mult,
- charismaExpGain = 20 * player.charisma_exp_mult;
- player.gainHackingExp(hackingExpGain);
- player.gainIntelligenceExp(BladeburnerConstants.BaseIntGain);
- player.gainCharismaExp(charismaExpGain);
- changeRank(bladeburner, player, 0.1 * BitNodeMultipliers.BladeburnerRank);
- bladeburner.getCurrentCity().improvePopulationEstimateByPercentage(eff * bladeburner.skillMultipliers.successChanceEstimate);
- if (bladeburner.logging.general) {
- bladeburner.log("Field analysis completed. Gained 0.1 rank, " + formatNumber(hackingExpGain, 1) + " hacking exp, and " + formatNumber(charismaExpGain, 1) + " charisma exp");
- }
- startAction(bladeburner,player, bladeburner.action); // Repeat action
- break;
- }
- case ActionTypes["Recruitment"]: {
- const successChance = getRecruitmentSuccessChance(bladeburner, player);
- if (Math.random() < successChance) {
- const expGain = 2 * BladeburnerConstants.BaseStatGain * bladeburner.actionTimeToComplete;
- player.gainCharismaExp(expGain);
- ++bladeburner.teamSize;
- if (bladeburner.logging.general) {
- bladeburner.log("Successfully recruited a team member! Gained " + formatNumber(expGain, 1) + " charisma exp");
- }
- } else {
- const expGain = BladeburnerConstants.BaseStatGain * bladeburner.actionTimeToComplete;
- player.gainCharismaExp(expGain);
- if (bladeburner.logging.general) {
- bladeburner.log("Failed to recruit a team member. Gained " + formatNumber(expGain, 1) + " charisma exp");
- }
- }
- startAction(bladeburner,player, bladeburner.action); // Repeat action
- break;
- }
- case ActionTypes["Diplomacy"]: {
- let eff = getDiplomacyEffectiveness(bladeburner, player);
- bladeburner.getCurrentCity().chaos *= eff;
- if (bladeburner.getCurrentCity().chaos < 0) { bladeburner.getCurrentCity().chaos = 0; }
- if (bladeburner.logging.general) {
- bladeburner.log(`Diplomacy completed. Chaos levels in the current city fell by ${numeralWrapper.formatPercentage(1 - eff)}`);
- }
- startAction(bladeburner,player, bladeburner.action); // Repeat Action
- break;
- }
- case ActionTypes["Hyperbolic Regeneration Chamber"]: {
- player.regenerateHp(BladeburnerConstants.HrcHpGain);
-
- const staminaGain = bladeburner.maxStamina * (BladeburnerConstants.HrcStaminaGain / 100);
- bladeburner.stamina = Math.min(bladeburner.maxStamina, bladeburner.stamina + staminaGain);
- startAction(bladeburner,player, bladeburner.action);
- if (bladeburner.logging.general) {
- bladeburner.log(`Rested in Hyperbolic Regeneration Chamber. Restored ${BladeburnerConstants.HrcHpGain} HP and gained ${numeralWrapper.formatStamina(staminaGain)} stamina`);
- }
- break;
- }
- default:
- console.error(`Bladeburner.completeAction() called for invalid action: ${bladeburner.action.type}`);
- break;
- }
-}
-
-export function changeRank(bladeburner: IBladeburner, player: IPlayer, change: number): void {
- if (isNaN(change)) {throw new Error("NaN passed into Bladeburner.changeRank()");}
- bladeburner.rank += change;
- if (bladeburner.rank < 0) {bladeburner.rank = 0;}
- bladeburner.maxRank = Math.max(bladeburner.rank, bladeburner.maxRank);
-
- var bladeburnersFactionName = "Bladeburners";
- if (factionExists(bladeburnersFactionName)) {
- var bladeburnerFac = Factions[bladeburnersFactionName];
- if (!(bladeburnerFac instanceof Faction)) {
- throw new Error("Could not properly get Bladeburner Faction object in Bladeburner UI Overview Faction button");
- }
- if (bladeburnerFac.isMember) {
- var favorBonus = 1 + (bladeburnerFac.favor / 100);
- bladeburnerFac.playerReputation += (BladeburnerConstants.RankToFactionRepFactor * change * player.faction_rep_mult * favorBonus);
- }
- }
-
- // Gain skill points
- var rankNeededForSp = (bladeburner.totalSkillPoints+1) * BladeburnerConstants.RanksPerSkillPoint;
- if (bladeburner.maxRank >= rankNeededForSp) {
- // Calculate how many skill points to gain
- var gainedSkillPoints = Math.floor((bladeburner.maxRank - rankNeededForSp) / BladeburnerConstants.RanksPerSkillPoint + 1);
- bladeburner.skillPoints += gainedSkillPoints;
- bladeburner.totalSkillPoints += gainedSkillPoints;
- }
-}
-
-export function processAction(bladeburner: IBladeburner, player: IPlayer, seconds: number): void {
- if (bladeburner.action.type === ActionTypes["Idle"]) return;
- if (bladeburner.actionTimeToComplete <= 0) {
- throw new Error(`Invalid actionTimeToComplete value: ${bladeburner.actionTimeToComplete}, type; ${bladeburner.action.type}`);
- }
- if (!(bladeburner.action instanceof ActionIdentifier)) {
- throw new Error("Bladeburner.action is not an ActionIdentifier Object");
- }
-
- // If the previous action went past its completion time, add to the next action
- // This is not added inmediatly in case the automation changes the action
- bladeburner.actionTimeCurrent += seconds + bladeburner.actionTimeOverflow;
- bladeburner.actionTimeOverflow = 0;
- if (bladeburner.actionTimeCurrent >= bladeburner.actionTimeToComplete) {
- bladeburner.actionTimeOverflow = bladeburner.actionTimeCurrent - bladeburner.actionTimeToComplete;
- return completeAction(bladeburner, player);
- }
-}
-
-export function startAction(bladeburner: IBladeburner, player: IPlayer, actionId: IActionIdentifier): void {
- if (actionId == null) return;
- bladeburner.action = actionId;
- bladeburner.actionTimeCurrent = 0;
- switch (actionId.type) {
- case ActionTypes["Idle"]:
- bladeburner.actionTimeToComplete = 0;
- break;
- case ActionTypes["Contract"]:
- try {
- const action = getActionObject(bladeburner, actionId);
- if (action == null) {
- throw new Error("Failed to get Contract Object for: " + actionId.name);
- }
- if (action.count < 1) {return resetAction(bladeburner);}
- bladeburner.actionTimeToComplete = action.getActionTime(bladeburner);
- } catch(e) {
- exceptionAlert(e);
- }
- break;
- case ActionTypes["Operation"]: {
- try {
- const action = getActionObject(bladeburner, actionId);
- if (action == null) {
- throw new Error ("Failed to get Operation Object for: " + actionId.name);
- }
- if (action.count < 1) {return resetAction(bladeburner);}
- if (actionId.name === "Raid" && bladeburner.getCurrentCity().commsEst === 0) {return resetAction(bladeburner);}
- bladeburner.actionTimeToComplete = action.getActionTime(bladeburner);
- } catch(e) {
- exceptionAlert(e);
- }
- break;
- }
- case ActionTypes["BlackOp"]:
- case ActionTypes["BlackOperation"]: {
- try {
- // Safety measure - don't repeat BlackOps that are already done
- if (bladeburner.blackops[actionId.name] != null) {
- resetAction(bladeburner);
- bladeburner.log("Error: Tried to start a Black Operation that had already been completed");
- break;
- }
-
- const action = getActionObject(bladeburner, actionId);
- if (action == null) {
- throw new Error("Failed to get BlackOperation object for: " + actionId.name);
- }
- bladeburner.actionTimeToComplete = action.getActionTime(bladeburner);
- } catch(e) {
- exceptionAlert(e);
- }
- break;
- }
- case ActionTypes["Recruitment"]:
- bladeburner.actionTimeToComplete = getRecruitmentTime(bladeburner, player);
- break;
- case ActionTypes["Training"]:
- case ActionTypes["FieldAnalysis"]:
- case ActionTypes["Field Analysis"]:
- bladeburner.actionTimeToComplete = 30;
- break;
- case ActionTypes["Diplomacy"]:
- case ActionTypes["Hyperbolic Regeneration Chamber"]:
- bladeburner.actionTimeToComplete = 60;
- break;
- default:
- throw new Error("Invalid Action Type in startAction(Bladeburner,player, ): " + actionId.type);
- break;
- }
-}
-
-export function calculateStaminaPenalty(bladeburner: IBladeburner): number {
- return Math.min(1, bladeburner.stamina / (0.5 * bladeburner.maxStamina));
-}
-
-export function calculateStaminaGainPerSecond(bladeburner: IBladeburner, player: IPlayer): number {
- const effAgility = player.agility * bladeburner.skillMultipliers.effAgi;
- const maxStaminaBonus = bladeburner.maxStamina / BladeburnerConstants.MaxStaminaToGainFactor;
- const gain = (BladeburnerConstants.StaminaGainPerSecond + maxStaminaBonus) * Math.pow(effAgility, 0.17);
- return gain * (bladeburner.skillMultipliers.stamina * player.bladeburner_stamina_gain_mult);
-}
-
-export function calculateMaxStamina(bladeburner: IBladeburner, player: IPlayer) {
- const effAgility = player.agility * bladeburner.skillMultipliers.effAgi;
- let maxStamina = (Math.pow(effAgility, 0.8) + bladeburner.staminaBonus) *
- bladeburner.skillMultipliers.stamina *
- player.bladeburner_max_stamina_mult;
- if (bladeburner.maxStamina !== maxStamina) {
- const oldMax = bladeburner.maxStamina;
- bladeburner.maxStamina = maxStamina;
- bladeburner.stamina = bladeburner.maxStamina * bladeburner.stamina / oldMax;
- }
- if (isNaN(maxStamina)) {throw new Error("Max Stamina calculated to be NaN in Bladeburner.calculateMaxStamina()");}
-}
-
-export function create(bladeburner: IBladeburner): void {
- bladeburner.contracts["Tracking"] = new Contract({
- name:"Tracking",
- desc:"Identify and locate Synthoids. This contract involves reconnaissance " +
- "and information-gathering ONLY. Do NOT engage. Stealth is of the utmost importance.
" +
- "Successfully completing Tracking contracts will slightly improve your Synthoid population estimate for " +
- "whatever city you are currently in.",
- baseDifficulty:125,difficultyFac:1.02,rewardFac:1.041,
- rankGain:0.3, hpLoss:0.5,
- count:getRandomInt(25, 150), countGrowth:getRandomInt(5, 75)/10,
- weights:{hack:0,str:0.05,def:0.05,dex:0.35,agi:0.35,cha:0.1, int:0.05},
- decays:{hack:0,str:0.91,def:0.91,dex:0.91,agi:0.91,cha:0.9, int:1},
- isStealth:true,
- });
- bladeburner.contracts["Bounty Hunter"] = new Contract({
- name:"Bounty Hunter",
- desc:"Hunt down and capture fugitive Synthoids. These Synthoids are wanted alive.
" +
- "Successfully completing a Bounty Hunter contract will lower the population in your " +
- "current city, and will also increase its chaos level.",
- baseDifficulty:250, difficultyFac:1.04,rewardFac:1.085,
- rankGain:0.9, hpLoss:1,
- count:getRandomInt(5, 150), countGrowth:getRandomInt(5, 75)/10,
- weights:{hack:0,str:0.15,def:0.15,dex:0.25,agi:0.25,cha:0.1, int:0.1},
- decays:{hack:0,str:0.91,def:0.91,dex:0.91,agi:0.91,cha:0.8, int:0.9},
- isKill:true,
- });
- bladeburner.contracts["Retirement"] = new Contract({
- name:"Retirement",
- desc:"Hunt down and retire (kill) rogue Synthoids.
" +
- "Successfully completing a Retirement contract will lower the population in your current " +
- "city, and will also increase its chaos level.",
- baseDifficulty:200, difficultyFac:1.03, rewardFac:1.065,
- rankGain:0.6, hpLoss:1,
- count:getRandomInt(5, 150), countGrowth:getRandomInt(5, 75)/10,
- weights:{hack:0,str:0.2,def:0.2,dex:0.2,agi:0.2,cha:0.1, int:0.1},
- decays:{hack:0,str:0.91,def:0.91,dex:0.91,agi:0.91,cha:0.8, int:0.9},
- isKill:true,
- });
-
- bladeburner.operations["Investigation"] = new Operation({
- name:"Investigation",
- desc:"As a field agent, investigate and identify Synthoid " +
- "populations, movements, and operations.
Successful " +
- "Investigation ops will increase the accuracy of your " +
- "synthoid data.
" +
- "You will NOT lose HP from failed Investigation ops.",
- baseDifficulty:400, difficultyFac:1.03,rewardFac:1.07,reqdRank:25,
- rankGain:2.2, rankLoss:0.2,
- count:getRandomInt(1, 100), countGrowth:getRandomInt(10, 40)/10,
- weights:{hack:0.25,str:0.05,def:0.05,dex:0.2,agi:0.1,cha:0.25, int:0.1},
- decays:{hack:0.85,str:0.9,def:0.9,dex:0.9,agi:0.9,cha:0.7, int:0.9},
- isStealth:true,
- });
- bladeburner.operations["Undercover Operation"] = new Operation({
- name:"Undercover Operation",
- desc:"Conduct undercover operations to identify hidden " +
- "and underground Synthoid communities and organizations.
" +
- "Successful Undercover ops will increase the accuracy of your synthoid " +
- "data.",
- baseDifficulty:500, difficultyFac:1.04, rewardFac:1.09, reqdRank:100,
- rankGain:4.4, rankLoss:0.4, hpLoss:2,
- count:getRandomInt(1, 100), countGrowth:getRandomInt(10, 40)/10,
- weights:{hack:0.2,str:0.05,def:0.05,dex:0.2,agi:0.2,cha:0.2, int:0.1},
- decays:{hack:0.8,str:0.9,def:0.9,dex:0.9,agi:0.9,cha:0.7, int:0.9},
- isStealth:true,
- });
- bladeburner.operations["Sting Operation"] = new Operation({
- name:"Sting Operation",
- desc:"Conduct a sting operation to bait and capture particularly " +
- "notorious Synthoid criminals.",
- baseDifficulty:650, difficultyFac:1.04, rewardFac:1.095, reqdRank:500,
- rankGain:5.5, rankLoss:0.5, hpLoss:2.5,
- count:getRandomInt(1, 150), countGrowth:getRandomInt(3, 40)/10,
- weights:{hack:0.25,str:0.05,def:0.05,dex:0.25,agi:0.1,cha:0.2, int:0.1},
- decays:{hack:0.8,str:0.85,def:0.85,dex:0.85,agi:0.85,cha:0.7, int:0.9},
- isStealth:true,
- });
- bladeburner.operations["Raid"] = new Operation({
- name:"Raid",
- desc:"Lead an assault on a known Synthoid community. Note that " +
- "there must be an existing Synthoid community in your current city " +
- "in order for this Operation to be successful.",
- baseDifficulty:800, difficultyFac:1.045, rewardFac:1.1, reqdRank:3000,
- rankGain:55,rankLoss:2.5,hpLoss:50,
- count:getRandomInt(1, 150), countGrowth:getRandomInt(2, 40)/10,
- weights:{hack:0.1,str:0.2,def:0.2,dex:0.2,agi:0.2,cha:0, int:0.1},
- decays:{hack:0.7,str:0.8,def:0.8,dex:0.8,agi:0.8,cha:0, int:0.9},
- isKill:true,
- });
- bladeburner.operations["Stealth Retirement Operation"] = new Operation({
- name:"Stealth Retirement Operation",
- desc:"Lead a covert operation to retire Synthoids. The " +
- "objective is to complete the task without " +
- "drawing any attention. Stealth and discretion are key.",
- baseDifficulty:1000, difficultyFac:1.05, rewardFac:1.11, reqdRank:20e3,
- rankGain:22, rankLoss:2, hpLoss:10,
- count:getRandomInt(1, 150), countGrowth:getRandomInt(1, 20)/10,
- weights:{hack:0.1,str:0.1,def:0.1,dex:0.3,agi:0.3,cha:0, int:0.1},
- decays:{hack:0.7,str:0.8,def:0.8,dex:0.8,agi:0.8,cha:0, int:0.9},
- isStealth:true, isKill:true,
- });
- bladeburner.operations["Assassination"] = new Operation({
- name:"Assassination",
- desc:"Assassinate Synthoids that have been identified as " +
- "important, high-profile social and political leaders " +
- "in the Synthoid communities.",
- baseDifficulty:1500, difficultyFac:1.06, rewardFac:1.14, reqdRank:50e3,
- rankGain:44, rankLoss:4, hpLoss:5,
- count:getRandomInt(1, 150), countGrowth:getRandomInt(1, 20)/10,
- weights:{hack:0.1,str:0.1,def:0.1,dex:0.3,agi:0.3,cha:0, int:0.1},
- decays:{hack:0.6,str:0.8,def:0.8,dex:0.8,agi:0.8,cha:0, int:0.8},
- isStealth:true, isKill:true,
- });
-}
-
-export function process(bladeburner: IBladeburner, player: IPlayer): void {
- // Edge case condition...if Operation Daedalus is complete trigger the BitNode
- if (redPillFlag === false && bladeburner.blackops.hasOwnProperty("Operation Daedalus")) {
- return hackWorldDaemon(player.bitNodeN);
- }
-
- // If the Player starts doing some other actions, set action to idle and alert
- if (Augmentations[AugmentationNames.BladesSimulacrum].owned === false && player.isWorking) {
- if (bladeburner.action.type !== ActionTypes["Idle"]) {
- let msg = "Your Bladeburner action was cancelled because you started doing something else.";
- if (bladeburner.automateEnabled) {
- msg += `
Your automation was disabled as well. You will have to re-enable it through the Bladeburner console`
- bladeburner.automateEnabled = false;
- }
- if (!Settings.SuppressBladeburnerPopup) {
- dialogBoxCreate(msg);
- }
- }
- resetAction(bladeburner);
- }
-
- // If the Player has no Stamina, set action to idle
- if (bladeburner.stamina <= 0) {
- bladeburner.log("Your Bladeburner action was cancelled because your stamina hit 0");
- resetAction(bladeburner);
- }
-
- // A 'tick' for this mechanic is one second (= 5 game cycles)
- if (bladeburner.storedCycles >= BladeburnerConstants.CyclesPerSecond) {
- let seconds = Math.floor(bladeburner.storedCycles / BladeburnerConstants.CyclesPerSecond);
- seconds = Math.min(seconds, 5); // Max of 5 'ticks'
- bladeburner.storedCycles -= seconds * BladeburnerConstants.CyclesPerSecond;
-
- // Stamina
- calculateMaxStamina(bladeburner, player);
- bladeburner.stamina += (calculateStaminaGainPerSecond(bladeburner, player) * seconds);
- bladeburner.stamina = Math.min(bladeburner.maxStamina, bladeburner.stamina);
-
- // Count increase for contracts/operations
- for (const contract of (Object.values(bladeburner.contracts) as Contract[])) {
- contract.count += (seconds * contract.countGrowth/BladeburnerConstants.ActionCountGrowthPeriod);
- }
- for (const op of (Object.values(bladeburner.operations) as Operation[])) {
- op.count += (seconds * op.countGrowth/BladeburnerConstants.ActionCountGrowthPeriod);
- }
-
- // Chaos goes down very slowly
- for (const cityName of BladeburnerConstants.CityNames) {
- const city = bladeburner.cities[cityName];
- if (!(city instanceof City)) {throw new Error("Invalid City object when processing passive chaos reduction in Bladeburner.process");}
- city.chaos -= (0.0001 * seconds);
- city.chaos = Math.max(0, city.chaos);
- }
-
- // Random Events
- bladeburner.randomEventCounter -= seconds;
- if (bladeburner.randomEventCounter <= 0) {
- randomEvent(bladeburner);
- // Add instead of setting because we might have gone over the required time for the event
- bladeburner.randomEventCounter += getRandomInt(240, 600);
- }
-
- processAction(bladeburner, player, seconds);
-
- // Automation
- if (bladeburner.automateEnabled) {
- // Note: Do NOT set bladeburner.action = bladeburner.automateActionHigh/Low since it creates a reference
- if (bladeburner.stamina <= bladeburner.automateThreshLow) {
- if (bladeburner.action.name !== bladeburner.automateActionLow.name || bladeburner.action.type !== bladeburner.automateActionLow.type) {
- bladeburner.action = new ActionIdentifier({type: bladeburner.automateActionLow.type, name: bladeburner.automateActionLow.name});
- startAction(bladeburner, player, bladeburner.action);
- }
- } else if (bladeburner.stamina >= bladeburner.automateThreshHigh) {
- if (bladeburner.action.name !== bladeburner.automateActionHigh.name || bladeburner.action.type !== bladeburner.automateActionHigh.type) {
- bladeburner.action = new ActionIdentifier({type: bladeburner.automateActionHigh.type, name: bladeburner.automateActionHigh.name});
- startAction(bladeburner, player, bladeburner.action);
- }
- }
- }
- }
-}
-
-
-
-
-
-
-
-
-export function prestige(bladeburner: IBladeburner): void {
- resetAction(bladeburner);
- const bladeburnerFac = Factions["Bladeburners"];
- if (bladeburner.rank >= BladeburnerConstants.RankNeededForFaction) {
- joinFaction(bladeburnerFac);
- }
-}
-
-export function storeCycles(bladeburner: IBladeburner, numCycles: number = 1): void {
- bladeburner.storedCycles += numCycles;
-}
-
-export function getCurrentCity(bladeburner: IBladeburner): City {
- const city = bladeburner.cities[bladeburner.city];
- if (!(city instanceof City)) {
- throw new Error("Bladeburner.getCurrentCity() did not properly return a City object");
- }
- return city;
-}
-
-export function upgradeSkill(bladeburner: IBladeburner, skill: Skill) {
- // This does NOT handle deduction of skill points
- const skillName = skill.name;
- if (bladeburner.skills[skillName]) {
- ++bladeburner.skills[skillName];
- } else {
- bladeburner.skills[skillName] = 1;
- }
- if (isNaN(bladeburner.skills[skillName]) || bladeburner.skills[skillName] < 0) {
- throw new Error("Level of Skill " + skillName + " is invalid: " + bladeburner.skills[skillName]);
- }
- updateSkillMultipliers(bladeburner);
-}
-
-// Bladeburner Console Window
-export function postToConsole(bladeburner: IBladeburner, input: string, saveToLogs: boolean = true) {
- const MaxConsoleEntries = 100;
- if (saveToLogs) {
- bladeburner.consoleLogs.push(input);
- if (bladeburner.consoleLogs.length > MaxConsoleEntries) {
- bladeburner.consoleLogs.shift();
- }
- }
-}
-
-export function log(bladeburner: IBladeburner, input: string) {
- // Adds a timestamp and then just calls postToConsole
- bladeburner.postToConsole(`[${getTimestamp()}] ${input}`);
-}
-
-////////////////////////////////////////////////////////////////////////////////
-//////////////////////////////// Netscript Fns /////////////////////////////////
-////////////////////////////////////////////////////////////////////////////////
-export function getTypeAndNameFromActionId(bladeburner: IBladeburner, actionId: IActionIdentifier): {type: string, name: string} {
- const res = {type: '', name: ''};
- const types = Object.keys(ActionTypes);
- for (let i = 0; i < types.length; ++i) {
- if (actionId.type === ActionTypes[types[i]]) {
- res.type = types[i];
- break;
- }
- }
- if (res.type == null) {res.type = "Idle";}
-
- res.name = actionId.name != null ? actionId.name : "Idle";
- return res;
-}
-export function getContractNamesNetscriptFn(bladeburner: IBladeburner): string[] {
- return Object.keys(bladeburner.contracts);
-}
-export function getOperationNamesNetscriptFn(bladeburner: IBladeburner): string[] {
- return Object.keys(bladeburner.operations);
-}
-export function getBlackOpNamesNetscriptFn(bladeburner: IBladeburner): string[] {
- return Object.keys(BlackOperations);
-}
-export function getGeneralActionNamesNetscriptFn(bladeburner: IBladeburner): string[] {
- return Object.keys(GeneralActions);
-}
-export function getSkillNamesNetscriptFn(bladeburner: IBladeburner): string[] {
- return Object.keys(Skills);
-}
-export function startActionNetscriptFn(bladeburner: IBladeburner, player: IPlayer, type: string, name: string, workerScript: WorkerScript) {
- const errorLogText = `Invalid action: type='${type}' name='${name}'`;
- const actionId = getActionIdFromTypeAndName(bladeburner, type, name);
- if (actionId == null) {
- workerScript.log("bladeburner.startAction", errorLogText);
- return false;
- }
-
- // Special logic for Black Ops
- if (actionId.type === ActionTypes["BlackOp"]) {
- // Can't start a BlackOp if you don't have the required rank
- const action = getActionObject(bladeburner, actionId);
- if(action == null) throw new Error('Action not found ${actionId.type}, ${actionId.name}');
- if(!(action instanceof BlackOperation)) throw new Error(`Action should be BlackOperation but isn't`);
- const blackOp = (action as BlackOperation);
- if (action.reqdRank > bladeburner.rank) {
- workerScript.log("bladeburner.startAction", `Insufficient rank to start Black Op '${actionId.name}'.`);
- return false;
- }
-
- // Can't start a BlackOp if its already been done
- if (bladeburner.blackops[actionId.name] != null) {
- workerScript.log("bladeburner.startAction", `Black Op ${actionId.name} has already been completed.`);
- return false;
- }
-
- // Can't start a BlackOp if you haven't done the one before it
- var blackops = [];
- for (const nm in BlackOperations) {
- if (BlackOperations.hasOwnProperty(nm)) {
- blackops.push(nm);
- }
- }
- blackops.sort(function(a, b) {
- return (BlackOperations[a].reqdRank - BlackOperations[b].reqdRank); // Sort black ops in intended order
- });
-
- let i = blackops.indexOf(actionId.name);
- if (i === -1) {
- workerScript.log("bladeburner.startAction", `Invalid Black Op: '${name}'`);
- return false;
- }
-
- if (i > 0 && bladeburner.blackops[blackops[i-1]] == null) {
- workerScript.log("bladeburner.startAction", `Preceding Black Op must be completed before starting '${actionId.name}'.`);
- return false;
- }
- }
-
- try {
- startAction(bladeburner, player, actionId);
- workerScript.log("bladeburner.startAction", `Starting bladeburner action with type '${type}' and name ${name}"`);
- return true;
- } catch(e) {
- resetAction(bladeburner);
- workerScript.log("bladeburner.startAction", errorLogText);
- return false;
- }
-}
-export function getActionTimeNetscriptFn(bladeburner: IBladeburner, player: IPlayer, type: string, name: string, workerScript: WorkerScript) {
- const errorLogText = `Invalid action: type='${type}' name='${name}'`
- const actionId = getActionIdFromTypeAndName(bladeburner, type, name);
- if (actionId == null) {
- workerScript.log("bladeburner.getActionTime", errorLogText);
- return -1;
- }
-
- const actionObj = getActionObject(bladeburner, actionId);
- if (actionObj == null) {
- workerScript.log("bladeburner.getActionTime", errorLogText);
- return -1;
- }
-
- switch (actionId.type) {
- case ActionTypes["Contract"]:
- case ActionTypes["Operation"]:
- case ActionTypes["BlackOp"]:
- case ActionTypes["BlackOperation"]:
- return actionObj.getActionTime(bladeburner);
- case ActionTypes["Training"]:
- case ActionTypes["Field Analysis"]:
- case ActionTypes["FieldAnalysis"]:
- return 30;
- case ActionTypes["Recruitment"]:
- return getRecruitmentTime(bladeburner, player);
- case ActionTypes["Diplomacy"]:
- case ActionTypes["Hyperbolic Regeneration Chamber"]:
- return 60;
- default:
- workerScript.log("bladeburner.getActionTime", errorLogText);
- return -1;
- }
-}
-export function getActionEstimatedSuccessChanceNetscriptFn(bladeburner: IBladeburner, player: IPlayer, type: string, name: string, workerScript: WorkerScript) {
- const errorLogText = `Invalid action: type='${type}' name='${name}'`
- const actionId = getActionIdFromTypeAndName(bladeburner, type, name);
- if (actionId == null) {
- workerScript.log("bladeburner.getActionEstimatedSuccessChance", errorLogText);
- return -1;
- }
-
- const actionObj = getActionObject(bladeburner, actionId);
- if (actionObj == null) {
- workerScript.log("bladeburner.getActionEstimatedSuccessChance", errorLogText);
- return -1;
- }
-
- switch (actionId.type) {
- case ActionTypes["Contract"]:
- case ActionTypes["Operation"]:
- case ActionTypes["BlackOp"]:
- case ActionTypes["BlackOperation"]:
- return actionObj.getSuccessChance(bladeburner, {est:true});
- case ActionTypes["Training"]:
- case ActionTypes["Field Analysis"]:
- case ActionTypes["FieldAnalysis"]:
- return 1;
- case ActionTypes["Recruitment"]:
- return getRecruitmentSuccessChance(bladeburner, player);
- default:
- workerScript.log("bladeburner.getActionEstimatedSuccessChance", errorLogText);
- return -1;
- }
-}
-export function getActionCountRemainingNetscriptFn(bladeburner: IBladeburner, type: string, name: string, workerScript: WorkerScript) {
- const errorLogText = `Invalid action: type='${type}' name='${name}'`;
- const actionId = getActionIdFromTypeAndName(bladeburner, type, name);
- if (actionId == null) {
- workerScript.log("bladeburner.getActionCountRemaining", errorLogText);
- return -1;
- }
-
- const actionObj = getActionObject(bladeburner, actionId);
- if (actionObj == null) {
- workerScript.log("bladeburner.getActionCountRemaining", errorLogText);
- return -1;
- }
-
- switch (actionId.type) {
- case ActionTypes["Contract"]:
- case ActionTypes["Operation"]:
- return Math.floor( actionObj.count );
- case ActionTypes["BlackOp"]:
- case ActionTypes["BlackOperation"]:
- if (bladeburner.blackops[name] != null) {
- return 0;
- } else {
- return 1;
- }
- case ActionTypes["Training"]:
- case ActionTypes["Field Analysis"]:
- case ActionTypes["FieldAnalysis"]:
- return Infinity;
- default:
- workerScript.log("bladeburner.getActionCountRemaining", errorLogText);
- return -1;
- }
-}
-export function getSkillLevelNetscriptFn(bladeburner: IBladeburner, skillName: string, workerScript: WorkerScript) {
- if (skillName === "" || !Skills.hasOwnProperty(skillName)) {
- workerScript.log("bladeburner.getSkillLevel", `Invalid skill: '${skillName}'`);
- return -1;
- }
-
- if (bladeburner.skills[skillName] == null) {
- return 0;
- } else {
- return bladeburner.skills[skillName];
- }
-}
-export function getSkillUpgradeCostNetscriptFn(bladeburner: IBladeburner, skillName: string, workerScript: WorkerScript) {
- if (skillName === "" || !Skills.hasOwnProperty(skillName)) {
- workerScript.log("bladeburner.getSkillUpgradeCost", `Invalid skill: '${skillName}'`);
- return -1;
- }
-
- const skill = Skills[skillName];
- if (bladeburner.skills[skillName] == null) {
- return skill.calculateCost(0);
- } else {
- return skill.calculateCost(bladeburner.skills[skillName]);
- }
-}
-export function upgradeSkillNetscriptFn(bladeburner: IBladeburner, skillName: string, workerScript: WorkerScript) {
- const errorLogText = `Invalid skill: '${skillName}'`;
- if (!Skills.hasOwnProperty(skillName)) {
- workerScript.log("bladeburner.upgradeSkill", errorLogText);
- return false;
- }
-
- const skill = Skills[skillName];
- let currentLevel = 0;
- if (bladeburner.skills[skillName] && !isNaN(bladeburner.skills[skillName])) {
- currentLevel = bladeburner.skills[skillName];
- }
- const cost = skill.calculateCost(currentLevel);
-
- if(skill.maxLvl && currentLevel >= skill.maxLvl) {
- workerScript.log("bladeburner.upgradeSkill", `Skill '${skillName}' is already maxed.`);
- return false;
- }
-
- if (bladeburner.skillPoints < cost) {
- workerScript.log("bladeburner.upgradeSkill", `You do not have enough skill points to upgrade ${skillName} (You have ${bladeburner.skillPoints}, you need ${cost})`);
- return false;
- }
-
- bladeburner.skillPoints -= cost;
- bladeburner.upgradeSkill(skill);
- workerScript.log("bladeburner.upgradeSkill", `'${skillName}' upgraded to level ${bladeburner.skills[skillName]}`);
- return true;
-}
-export function getTeamSizeNetscriptFn(bladeburner: IBladeburner, type: string, name: string, workerScript: WorkerScript): number {
- if (type === "" && name === "") {
- return bladeburner.teamSize;
- }
-
- const errorLogText = `Invalid action: type='${type}' name='${name}'`;
- const actionId = getActionIdFromTypeAndName(bladeburner, type, name);
- if (actionId == null) {
- workerScript.log("bladeburner.getTeamSize", errorLogText);
- return -1;
- }
-
- const actionObj = getActionObject(bladeburner, actionId);
- if (actionObj == null) {
- workerScript.log("bladeburner.getTeamSize", errorLogText);
- return -1;
- }
-
- if (actionId.type === ActionTypes["Operation"] ||
- actionId.type === ActionTypes["BlackOp"] ||
- actionId.type === ActionTypes["BlackOperation"]) {
- return actionObj.teamCount;
- } else {
- return 0;
- }
-}
-export function setTeamSizeNetscriptFn(bladeburner: IBladeburner, type: string, name: string, size: number, workerScript: WorkerScript): number {
- const errorLogText = `Invalid action: type='${type}' name='${name}'`;
- const actionId = getActionIdFromTypeAndName(bladeburner, type, name);
- if (actionId == null) {
- workerScript.log("bladeburner.setTeamSize", errorLogText);
- return -1;
- }
-
- if (actionId.type !== ActionTypes["Operation"] &&
- actionId.type !== ActionTypes["BlackOp"] &&
- actionId.type !== ActionTypes["BlackOperation"]) {
- workerScript.log("bladeburner.setTeamSize", "Only valid for 'Operations' and 'BlackOps'");
- return -1;
- }
-
- const actionObj = getActionObject(bladeburner, actionId);
- if (actionObj == null) {
- workerScript.log("bladeburner.setTeamSize", errorLogText);
- return -1;
- }
-
- let sanitizedSize = Math.round(size);
- if (isNaN(sanitizedSize) || sanitizedSize < 0) {
- workerScript.log("bladeburner.setTeamSize", `Invalid size: ${size}`);
- return -1;
- }
- if (bladeburner.teamSize < sanitizedSize) {sanitizedSize = bladeburner.teamSize;}
- actionObj.teamCount = sanitizedSize;
- workerScript.log("bladeburner.setTeamSize", `Team size for '${name}' set to ${sanitizedSize}.`);
- return sanitizedSize;
-}
-export function joinBladeburnerFactionNetscriptFn(bladeburner: IBladeburner, workerScript: WorkerScript): boolean {
- var bladeburnerFac = Factions["Bladeburners"];
- if (bladeburnerFac.isMember) {
- return true;
- } else if (bladeburner.rank >= BladeburnerConstants.RankNeededForFaction) {
- joinFaction(bladeburnerFac);
- workerScript.log("bladeburner.joinBladeburnerFaction", "Joined Bladeburners faction.");
- return true;
- } else {
- workerScript.log("bladeburner.joinBladeburnerFaction", `You do not have the required rank (${bladeburner.rank}/${BladeburnerConstants.RankNeededForFaction}).`);
- return false;
- }
-}
+Reviver.constructors.Bladeburner = Bladeburner;
\ No newline at end of file
diff --git a/src/Bladeburner/IBladeburner.ts b/src/Bladeburner/IBladeburner.ts
index a9cb20e01..b69c2c40a 100644
--- a/src/Bladeburner/IBladeburner.ts
+++ b/src/Bladeburner/IBladeburner.ts
@@ -1,6 +1,7 @@
import { IActionIdentifier } from "./IActionIdentifier";
import { City } from "./City";
import { Skill } from "./Skill";
+import { IAction } from "./IAction";
import { IPlayer } from "../PersonObjects/IPlayer";
import { WorkerScript } from "../Netscript/WorkerScript";
@@ -74,4 +75,31 @@ export interface IBladeburner {
getTeamSizeNetscriptFn(type: string, name: string, workerScript: WorkerScript): number;
setTeamSizeNetscriptFn(type: string, name: string, size: number, workerScript: WorkerScript): number;
joinBladeburnerFactionNetscriptFn(workerScript: WorkerScript): boolean;
-}
+ getActionIdFromTypeAndName(type: string, name: string): IActionIdentifier | null;
+ executeStartConsoleCommand(player: IPlayer, args: string[]): void;
+ executeSkillConsoleCommand(args: string[]): void;
+ executeLogConsoleCommand(args: string[]): void;
+ executeHelpConsoleCommand(args: string[]): void;
+ executeAutomateConsoleCommand(args: string[]): void;
+ parseCommandArguments(command: string): string[];
+ executeConsoleCommand(player: IPlayer, command: string): void;
+ triggerMigration(sourceCityName: string): void;
+ triggerPotentialMigration(sourceCityName: string, chance: number): void;
+ randomEvent(): void;
+ gainActionStats(player: IPlayer, action: IAction, success: boolean): void;
+ getDiplomacyEffectiveness(player: IPlayer): number;
+ getRecruitmentSuccessChance(player: IPlayer): number;
+ getRecruitmentTime(player: IPlayer): number;
+ resetSkillMultipliers(): void;
+ updateSkillMultipliers(): void;
+ completeOperation(success: boolean): void;
+ getActionObject(actionId: IActionIdentifier): IAction | null;
+ completeContract(success: boolean): void;
+ completeAction(player: IPlayer): void;
+ changeRank(player: IPlayer, change: number): void;
+ processAction(player: IPlayer, seconds: number): void;
+ calculateStaminaGainPerSecond(player: IPlayer): number;
+ calculateMaxStamina(player: IPlayer): void;
+ create(): void;
+ process(player: IPlayer): void;
+}
\ No newline at end of file
diff --git a/src/Bladeburner/ui/BlackOpElem.tsx b/src/Bladeburner/ui/BlackOpElem.tsx
index c5b9c8f5a..f1961c35a 100644
--- a/src/Bladeburner/ui/BlackOpElem.tsx
+++ b/src/Bladeburner/ui/BlackOpElem.tsx
@@ -10,7 +10,6 @@ import { createPopup } from "../../ui/React/createPopup";
import { TeamSizePopup } from "./TeamSizePopup";
import { IBladeburner } from "../IBladeburner";
import { IPlayer } from "../../PersonObjects/IPlayer";
-import { startAction } from "../Bladeburner";
interface IProps {
bladeburner: IBladeburner;
@@ -35,7 +34,7 @@ export function BlackOpElem(props: IProps): React.ReactElement {
function onStart() {
props.bladeburner.action.type = ActionTypes.BlackOperation;
props.bladeburner.action.name = props.action.name;
- startAction(props.bladeburner, props.player, props.bladeburner.action);
+ props.bladeburner.startAction(props.player, props.bladeburner.action);
setRerender(old => !old);
}
diff --git a/src/Bladeburner/ui/ContractElem.tsx b/src/Bladeburner/ui/ContractElem.tsx
index 789317e11..d479b4299 100644
--- a/src/Bladeburner/ui/ContractElem.tsx
+++ b/src/Bladeburner/ui/ContractElem.tsx
@@ -9,7 +9,6 @@ import { stealthIcon, killIcon } from "../data/Icons";
import { BladeburnerConstants } from "../data/Constants";
import { IBladeburner } from "../IBladeburner";
import { IPlayer } from "../../PersonObjects/IPlayer";
-import { startAction } from "../Bladeburner";
interface IProps {
bladeburner: IBladeburner;
@@ -29,19 +28,19 @@ export function ContractElem(props: IProps): React.ReactElement {
function onStart() {
props.bladeburner.action.type = ActionTypes.Contract;
props.bladeburner.action.name = props.action.name;
- startAction(props.bladeburner, props.player, props.bladeburner.action);
+ props.bladeburner.startAction(props.player, props.bladeburner.action);
setRerender(old => !old);
}
function increaseLevel() {
++props.action.level;
- if (isActive) startAction(props.bladeburner, props.player, props.bladeburner.action);
+ if (isActive) props.bladeburner.startAction(props.player, props.bladeburner.action);
setRerender(old => !old);
}
function decreaseLevel() {
--props.action.level;
- if (isActive) startAction(props.bladeburner, props.player, props.bladeburner.action);
+ if (isActive) props.bladeburner.startAction(props.player, props.bladeburner.action);
setRerender(old => !old);
}
diff --git a/src/Bladeburner/ui/GeneralActionElem.tsx b/src/Bladeburner/ui/GeneralActionElem.tsx
index bd312035f..d6a23e8bb 100644
--- a/src/Bladeburner/ui/GeneralActionElem.tsx
+++ b/src/Bladeburner/ui/GeneralActionElem.tsx
@@ -10,8 +10,6 @@ import { BladeburnerConstants } from "../data/Constants";
import { IBladeburner } from "../IBladeburner";
import { IPlayer } from "../../PersonObjects/IPlayer";
-import { startAction } from "../Bladeburner";
-
interface IProps {
bladeburner: IBladeburner;
player: IPlayer;
@@ -26,7 +24,7 @@ export function GeneralActionElem(props: IProps): React.ReactElement {
function onStart() {
props.bladeburner.action.type = ActionTypes[(props.action.name as string)];
props.bladeburner.action.name = props.action.name;
- startAction(props.bladeburner, props.player, props.bladeburner.action);
+ props.bladeburner.startAction(props.player, props.bladeburner.action);
setRerender(old => !old);
}
diff --git a/src/Bladeburner/ui/OperationElem.tsx b/src/Bladeburner/ui/OperationElem.tsx
index f3d07eb62..482d08703 100644
--- a/src/Bladeburner/ui/OperationElem.tsx
+++ b/src/Bladeburner/ui/OperationElem.tsx
@@ -11,7 +11,6 @@ import { createPopup } from "../../ui/React/createPopup";
import { TeamSizePopup } from "./TeamSizePopup";
import { IBladeburner } from "../IBladeburner";
import { IPlayer } from "../../PersonObjects/IPlayer";
-import { startAction } from "../Bladeburner";
interface IProps {
bladeburner: IBladeburner;
@@ -31,7 +30,7 @@ export function OperationElem(props: IProps): React.ReactElement {
function onStart() {
props.bladeburner.action.type = ActionTypes.Operation;
props.bladeburner.action.name = props.action.name;
- startAction(props.bladeburner, props.player, props.bladeburner.action);
+ props.bladeburner.startAction(props.player, props.bladeburner.action);
setRerender(old => !old);
}
@@ -46,13 +45,13 @@ export function OperationElem(props: IProps): React.ReactElement {
function increaseLevel() {
++props.action.level;
- if (isActive) startAction(props.bladeburner, props.player, props.bladeburner.action);
+ if (isActive) props.bladeburner.startAction(props.player, props.bladeburner.action);
setRerender(old => !old);
}
function decreaseLevel() {
--props.action.level;
- if (isActive) startAction(props.bladeburner, props.player, props.bladeburner.action);
+ if (isActive) props.bladeburner.startAction(props.player, props.bladeburner.action);
setRerender(old => !old);
}
diff --git a/src/engine.jsx b/src/engine.jsx
index 1c5379aea..4e9cdc854 100644
--- a/src/engine.jsx
+++ b/src/engine.jsx
@@ -884,7 +884,7 @@ const Engine = {
}
if (Player.bladeburner instanceof Bladeburner) {
try {
- ProcessBladeburner(Player.bladeburner, Player);
+ Player.bladeburner.process(Player);
} catch(e) {
exceptionAlert("Exception caught in Bladeburner.process(): " + e);
}