bitburner-src/src/NetscriptFunctions/Bladeburner.ts

322 lines
13 KiB
TypeScript
Raw Normal View History

import type { Bladeburner as INetscriptBladeburner } from "@nsdefs";
import type { Action, LevelableAction } from "../Bladeburner/Types";
import type { InternalAPI, NetscriptContext } from "../Netscript/APIWrapper";
import { Player } from "@player";
import { BladeActionType, BladeContractName, BladeGeneralActionName, BladeOperationName, BladeSkillName } from "@enums";
import { Bladeburner, BladeburnerPromise } from "../Bladeburner/Bladeburner";
import { currentNodeMults } from "../BitNode/BitNodeMultipliers";
2022-08-08 19:43:41 +02:00
import { helpers } from "../Netscript/NetscriptHelpers";
import { getEnumHelper } from "../utils/EnumHelper";
import { Skills } from "../Bladeburner/data/Skills";
import { assertString } from "../Netscript/TypeAssertion";
import { BlackOperations, blackOpsArray } from "../Bladeburner/data/BlackOperations";
2021-10-14 09:22:02 +02:00
2022-08-09 21:41:47 +02:00
export function NetscriptBladeburner(): InternalAPI<INetscriptBladeburner> {
2022-09-06 15:07:12 +02:00
const checkBladeburnerAccess = function (ctx: NetscriptContext): void {
getBladeburner(ctx);
return;
};
const getBladeburner = function (ctx: NetscriptContext): Bladeburner {
const apiAccess = Player.bitNodeN === 7 || Player.sourceFileLvl(7) > 0;
2021-10-14 09:22:02 +02:00
if (!apiAccess) {
throw helpers.errorMessage(ctx, "You have not unlocked the bladeburner API.", "API ACCESS");
2021-10-14 09:22:02 +02:00
}
2022-09-06 15:07:12 +02:00
const bladeburner = Player.bladeburner;
if (!bladeburner)
throw helpers.errorMessage(ctx, "You must be a member of the Bladeburner division to use this API.");
2022-09-06 15:07:12 +02:00
return bladeburner;
2021-10-14 09:22:02 +02:00
};
function getAction(ctx: NetscriptContext, type: unknown, name: unknown): Action {
2022-09-06 15:07:12 +02:00
const bladeburner = Player.bladeburner;
assertString(ctx, "type", type);
assertString(ctx, "name", name);
2021-10-14 09:22:02 +02:00
if (bladeburner === null) throw new Error("Must have joined bladeburner");
const action = bladeburner.getActionFromTypeAndName(type, name);
if (!action) throw helpers.errorMessage(ctx, `Invalid action type='${type}', name='${name}'`);
return action;
}
2021-10-14 09:22:02 +02:00
function isLevelableAction(action: Action): action is LevelableAction {
return action.type === BladeActionType.contract || action.type === BladeActionType.operation;
}
function getLevelableAction(ctx: NetscriptContext, type: unknown, name: unknown): LevelableAction {
const action = getAction(ctx, type, name);
if (!isLevelableAction(action)) {
throw helpers.errorMessage(
ctx,
`Actions of type ${action.type} are not levelable, ${ctx.functionPath} requires a levelable action`,
);
}
return action;
}
2021-10-14 09:22:02 +02:00
return {
NETSCRIPT: ns.sleeve.getSleeve added. getPlayer and getSleeve can both be used for formulas. (#200) * BREAKING CHANGE: Removed getSleeveStats and getSleeveInformation because this info is provided by getSleeve in a more usable form. * BREAKING CHANGE: Removed tor, inBladeburner, and hasCorporation fields from ns.getPlayer. Functionality still exists via added functions ns.hasTorRouter, ns.corporation.hasCorporation, and ns.bladeburner.inBladeburner. * Separated ns definitions for Person, Sleeve, and Player interfaces with both Player and Sleeve just extending Person. Added getSleeve, which provides a Sleeve object similar to getPlayer. * Renamed the sleeve ns layer's interface as sleeve lowercase because of name conflict. todo: May move all the ns layers interface names to lowercase for consistency * Added ns.formulas.work.crimeSuccessChance and reworked to allow both sleeve and player calculations. * Removed internal Person.getIntelligenceBonus function which was just a wrapper for calculateIntelligenceBonus. Any use of the former in formulas creates a conflict where ns-provided Person objects throw an error. * Renamed helpers.player to helpers.person for netscript person validation. Reduced number of fields validated due to Person being a smaller interface. * Fixed bug in bladeburner where Player multipliers and int were being used no matter which person was performing the task * Fixed leak of Player.jobs at ns.getPlayer * Person / Player / Sleeve classes now implement the netscript equivalent interfaces. Netscript helper for person no longer asserts that it's a real Person class member, only that it's a Person interface. Functions that use netscript persons have been changed to expect just a person interface to prevent needing this incorrect type assertion.
2022-11-09 13:26:26 +01:00
inBladeburner: () => () => !!Player.bladeburner,
getContractNames: (ctx) => () => {
getBladeburner(ctx);
return Object.values(BladeContractName);
2021-10-14 09:22:02 +02:00
},
getOperationNames: (ctx) => () => {
getBladeburner(ctx);
return Object.values(BladeOperationName);
2021-10-14 09:22:02 +02:00
},
getBlackOpNames: (ctx) => () => {
getBladeburner(ctx);
// Ensures they are sent in the correct order
return blackOpsArray.map((blackOp) => blackOp.name);
2021-10-14 09:22:02 +02:00
},
getNextBlackOp: (ctx) => () => {
const bladeburner = getBladeburner(ctx);
if (bladeburner.numBlackOpsComplete >= blackOpsArray.length) return null;
const blackOp = blackOpsArray[bladeburner.numBlackOpsComplete];
return { name: blackOp.name, rank: blackOp.reqdRank };
},
getBlackOpRank: (ctx) => (_blackOpName) => {
checkBladeburnerAccess(ctx);
const blackOpName = getEnumHelper("BladeBlackOpName").nsGetMember(ctx, _blackOpName);
return BlackOperations[blackOpName].reqdRank;
},
getGeneralActionNames: (ctx) => () => {
getBladeburner(ctx);
return Object.values(BladeGeneralActionName);
2021-10-14 09:22:02 +02:00
},
getSkillNames: (ctx) => () => {
getBladeburner(ctx);
return Object.values(BladeSkillName);
2021-10-14 09:22:02 +02:00
},
startAction: (ctx) => (type, name) => {
const bladeburner = getBladeburner(ctx);
const action = getAction(ctx, type, name);
const attempt = bladeburner.startAction(action.id);
helpers.log(ctx, () => attempt.message);
return !!attempt.success;
},
stopBladeburnerAction: (ctx) => () => {
2022-09-06 15:07:12 +02:00
const bladeburner = getBladeburner(ctx);
helpers.log(ctx, () => `Stopping current Bladeburner action.`);
2021-10-14 09:22:02 +02:00
return bladeburner.resetAction();
},
getCurrentAction: (ctx) => () => {
2022-09-06 15:07:12 +02:00
const bladeburner = getBladeburner(ctx);
// Temporary bad return type to not be an API break (idle should just return null)
if (!bladeburner.action) return { type: "Idle", name: "Idle" };
return { ...bladeburner.action };
2021-10-14 09:22:02 +02:00
},
getActionTime: (ctx) => (type, name) => {
const bladeburner = getBladeburner(ctx);
const action = getAction(ctx, type, name);
// return ms instead of seconds
return action.getActionTime(bladeburner, Player) * 1000;
},
getActionCurrentTime: (ctx) => () => {
2022-09-06 15:07:12 +02:00
const bladeburner = getBladeburner(ctx);
try {
2022-05-22 06:40:32 +02:00
const timecomputed =
Math.min(bladeburner.actionTimeCurrent + bladeburner.actionTimeOverflow, bladeburner.actionTimeToComplete) *
1000;
return timecomputed;
2022-07-15 07:51:30 +02:00
} catch (e: unknown) {
throw helpers.errorMessage(ctx, String(e));
}
},
getActionEstimatedSuccessChance: (ctx) => (type, name) => {
2023-01-02 19:28:31 +01:00
const bladeburner = getBladeburner(ctx);
const action = getAction(ctx, type, name);
return action.getSuccessRange(bladeburner, Player);
},
getActionRepGain: (ctx) => (type, name, _level) => {
2023-01-02 19:28:31 +01:00
checkBladeburnerAccess(ctx);
const action = getAction(ctx, type, name);
const level = isLevelableAction(action) ? helpers.number(ctx, "level", _level ?? action.level) : 1;
const rewardMultiplier = isLevelableAction(action) ? Math.pow(action.rewardFac, level - 1) : 1;
return action.rankGain * rewardMultiplier * currentNodeMults.BladeburnerRank;
},
getActionCountRemaining: (ctx) => (type, name) => {
2023-01-02 19:28:31 +01:00
const bladeburner = getBladeburner(ctx);
const action = getAction(ctx, type, name);
switch (action.type) {
case BladeActionType.general:
return Infinity;
case BladeActionType.blackOp:
return bladeburner.numBlackOpsComplete > action.n ? 0 : 1;
case BladeActionType.contract:
case BladeActionType.operation:
return action.count;
}
},
getActionMaxLevel: (ctx) => (type, name) => {
checkBladeburnerAccess(ctx);
const action = getLevelableAction(ctx, type, name);
return action.maxLevel;
},
getActionCurrentLevel: (ctx) => (type, name) => {
checkBladeburnerAccess(ctx);
const action = getLevelableAction(ctx, type, name);
return action.level;
},
getActionAutolevel: (ctx) => (type, name) => {
checkBladeburnerAccess(ctx);
const action = getLevelableAction(ctx, type, name);
return action.autoLevel;
},
getActionSuccesses: (ctx) => (type, name) => {
checkBladeburnerAccess(ctx);
const action = getLevelableAction(ctx, type, name);
return action.successes;
},
2022-05-08 01:08:07 +02:00
setActionAutolevel:
(ctx) =>
(type, name, _autoLevel = true) => {
2022-08-08 19:43:41 +02:00
const autoLevel = !!_autoLevel;
2022-05-08 01:08:07 +02:00
checkBladeburnerAccess(ctx);
const action = getLevelableAction(ctx, type, name);
2022-05-08 01:08:07 +02:00
action.autoLevel = autoLevel;
helpers.log(ctx, () => `Autolevel for ${action.name} has been ${autoLevel ? "enabled" : "disabled"}`);
2022-05-08 01:08:07 +02:00
},
setActionLevel: (ctx) => (type, name, _level) => {
const level = helpers.positiveInteger(ctx, "level", _level ?? 1);
checkBladeburnerAccess(ctx);
const action = getLevelableAction(ctx, type, name);
if (level < 1 || level > action.maxLevel) {
throw helpers.errorMessage(ctx, `Level must be between 1 and ${action.maxLevel}, is ${level}`);
}
action.level = level;
helpers.log(ctx, () => `Set level for ${action.name} to ${level}`);
},
getRank: (ctx) => () => {
2022-09-06 15:07:12 +02:00
const bladeburner = getBladeburner(ctx);
2021-10-14 09:22:02 +02:00
return bladeburner.rank;
},
getSkillPoints: (ctx) => () => {
2022-09-06 15:07:12 +02:00
const bladeburner = getBladeburner(ctx);
2021-10-14 09:22:02 +02:00
return bladeburner.skillPoints;
},
getSkillLevel: (ctx) => (_skillName) => {
const bladeburner = getBladeburner(ctx);
const skillName = getEnumHelper("BladeSkillName").nsGetMember(ctx, _skillName, "skillName");
return bladeburner.getSkillLevel(skillName);
},
getSkillUpgradeCost: (ctx) => (_skillName, _count) => {
const bladeburner = getBladeburner(ctx);
const skillName = getEnumHelper("BladeSkillName").nsGetMember(ctx, _skillName, "skillName");
const count = helpers.positiveSafeInteger(ctx, "count", _count ?? 1);
const currentLevel = bladeburner.getSkillLevel(skillName);
return Skills[skillName].calculateCost(currentLevel, count);
},
upgradeSkill: (ctx) => (_skillName, _count) => {
const bladeburner = getBladeburner(ctx);
const skillName = getEnumHelper("BladeSkillName").nsGetMember(ctx, _skillName, "skillName");
const count = helpers.positiveSafeInteger(ctx, "count", _count ?? 1);
const attempt = bladeburner.upgradeSkill(skillName, count);
helpers.log(ctx, () => attempt.message);
return !!attempt.success;
},
getTeamSize: (ctx) => (type, name) => {
const bladeburner = getBladeburner(ctx);
if (!type && !name) return bladeburner.teamSize;
const action = getAction(ctx, type, name);
switch (action.type) {
case BladeActionType.general:
case BladeActionType.contract:
return 0;
case BladeActionType.blackOp:
case BladeActionType.operation:
return action.teamCount;
}
},
setTeamSize: (ctx) => (type, name, _size) => {
const bladeburner = getBladeburner(ctx);
const action = getAction(ctx, type, name);
const size = helpers.positiveInteger(ctx, "size", _size);
if (size > bladeburner.teamSize) {
helpers.log(ctx, () => `Failed to set team size due to not enough team members.`);
return -1;
}
switch (action.type) {
case BladeActionType.contract:
case BladeActionType.general:
helpers.log(ctx, () => "Only valid for Operations and Black Operations");
return -1;
case BladeActionType.blackOp:
case BladeActionType.operation: {
action.teamCount = size;
helpers.log(ctx, () => `Set team size for ${action.name} to ${size}`);
return size;
}
}
},
getCityEstimatedPopulation: (ctx) => (_cityName) => {
const bladeburner = getBladeburner(ctx);
const cityName = getEnumHelper("CityName").nsGetMember(ctx, _cityName);
return bladeburner.cities[cityName].popEst;
},
getCityCommunities: (ctx) => (_cityName) => {
const bladeburner = getBladeburner(ctx);
const cityName = getEnumHelper("CityName").nsGetMember(ctx, _cityName);
return bladeburner.cities[cityName].comms;
},
getCityChaos: (ctx) => (_cityName) => {
const bladeburner = getBladeburner(ctx);
const cityName = getEnumHelper("CityName").nsGetMember(ctx, _cityName);
return bladeburner.cities[cityName].chaos;
},
getCity: (ctx) => () => {
2022-09-06 15:07:12 +02:00
const bladeburner = getBladeburner(ctx);
2021-10-14 09:22:02 +02:00
return bladeburner.city;
},
switchCity: (ctx) => (_cityName) => {
const bladeburner = getBladeburner(ctx);
const cityName = getEnumHelper("CityName").nsGetMember(ctx, _cityName);
bladeburner.city = cityName;
return true;
},
getStamina: (ctx) => () => {
2022-09-06 15:07:12 +02:00
const bladeburner = getBladeburner(ctx);
2021-10-14 09:22:02 +02:00
return [bladeburner.stamina, bladeburner.maxStamina];
},
joinBladeburnerFaction: (ctx) => () => {
2022-09-06 15:07:12 +02:00
const bladeburner = getBladeburner(ctx);
const attempt = bladeburner.joinFaction();
helpers.log(ctx, () => attempt.message);
return !!attempt.success;
2021-10-14 09:22:02 +02:00
},
joinBladeburnerDivision: (ctx) => () => {
2022-09-06 15:07:12 +02:00
if (Player.bitNodeN === 7 || Player.sourceFileLvl(7) > 0) {
if (currentNodeMults.BladeburnerRank === 0) {
return false; // Disabled in this bitnode
2021-10-14 09:22:02 +02:00
}
if (Player.bladeburner) {
2021-10-14 09:22:02 +02:00
return true; // Already member
} else if (
2022-09-06 15:07:12 +02:00
Player.skills.strength >= 100 &&
Player.skills.defense >= 100 &&
Player.skills.dexterity >= 100 &&
Player.skills.agility >= 100
2021-10-14 09:22:02 +02:00
) {
Player.startBladeburner();
2022-08-08 21:51:50 +02:00
helpers.log(ctx, () => "You have been accepted into the Bladeburner division");
2021-10-14 09:22:02 +02:00
return true;
} else {
2022-08-08 21:51:50 +02:00
helpers.log(ctx, () => "You do not meet the requirements for joining the Bladeburner division");
2021-10-14 09:22:02 +02:00
return false;
}
}
return false;
2021-10-14 09:22:02 +02:00
},
getBonusTime: (ctx) => () => {
2022-09-06 15:07:12 +02:00
const bladeburner = getBladeburner(ctx);
2022-03-30 02:44:27 +02:00
return Math.round(bladeburner.storedCycles / 5) * 1000;
2021-10-14 09:22:02 +02:00
},
nextUpdate: (ctx) => () => {
checkBladeburnerAccess(ctx);
if (!BladeburnerPromise.promise)
BladeburnerPromise.promise = new Promise<number>((res) => (BladeburnerPromise.resolve = res));
return BladeburnerPromise.promise;
},
2021-10-14 09:22:02 +02:00
};
}