MISC: Refactor BLADEBURNER Identifier Lookup (#1646)

This commit is contained in:
Denis Čahuk 2024-09-15 02:39:18 +02:00 committed by GitHub
parent f39402e8be
commit cde5e3f6ae
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 186 additions and 108 deletions

@ -1,9 +1,10 @@
import type { Bladeburner } from "../Bladeburner";
import type { Availability, ActionIdentifier } from "../Types";
import type { ActionIdFor, Availability } from "../Types";
import { BladeburnerActionType, BladeburnerBlackOpName } from "@enums";
import { ActionClass, ActionParams } from "./Action";
import { operationSkillSuccessBonus, operationTeamSuccessBonus } from "./Operation";
import { getEnumHelper } from "../../utils/EnumHelper";
interface BlackOpParams {
name: BladeburnerBlackOpName;
@ -12,13 +13,22 @@ interface BlackOpParams {
}
export class BlackOperation extends ActionClass {
type: BladeburnerActionType.BlackOp = BladeburnerActionType.BlackOp;
name: BladeburnerBlackOpName;
readonly type: BladeburnerActionType.BlackOp = BladeburnerActionType.BlackOp;
readonly name: BladeburnerBlackOpName;
n: number;
reqdRank: number;
teamCount = 0;
get id(): ActionIdentifier {
return { type: this.type, name: this.name };
get id() {
return BlackOperation.createId(this.name);
}
static createId(name: BladeburnerBlackOpName): ActionIdFor<BlackOperation> {
return { type: BladeburnerActionType.BlackOp, name };
}
static IsAcceptedName(name: unknown): name is BladeburnerBlackOpName {
return getEnumHelper("BladeburnerBlackOpName").isMember(name);
}
constructor(params: ActionParams & BlackOpParams) {

@ -1,20 +1,30 @@
import type { Bladeburner } from "../Bladeburner";
import type { ActionIdentifier } from "../Types";
import type { ActionIdFor } from "../Types";
import { Generic_fromJSON, IReviverValue, constructorsForReviver } from "../../utils/JSONReviver";
import { BladeburnerActionType, BladeburnerContractName, BladeburnerMultName } from "../Enums";
import { LevelableActionClass, LevelableActionParams } from "./LevelableAction";
import { getEnumHelper } from "../../utils/EnumHelper";
export class Contract extends LevelableActionClass {
type: BladeburnerActionType.Contract = BladeburnerActionType.Contract;
name: BladeburnerContractName = BladeburnerContractName.Tracking;
get id(): ActionIdentifier {
return { type: this.type, name: this.name };
readonly type: BladeburnerActionType.Contract = BladeburnerActionType.Contract;
readonly name: BladeburnerContractName;
get id() {
return Contract.createId(this.name);
}
static IsAcceptedName(name: unknown): name is BladeburnerContractName {
return getEnumHelper("BladeburnerContractName").isMember(name);
}
static createId(name: BladeburnerContractName): ActionIdFor<Contract> {
return { type: BladeburnerActionType.Contract, name };
}
constructor(params: (LevelableActionParams & { name: BladeburnerContractName }) | null = null) {
super(params);
if (params) this.name = params.name;
this.name = params?.name ?? BladeburnerContractName.Tracking;
}
getActionTypeSkillSuccessBonus(inst: Bladeburner): number {

@ -1,10 +1,11 @@
import type { Person } from "../../PersonObjects/Person";
import type { Bladeburner } from "../Bladeburner";
import type { ActionIdentifier } from "../Types";
import type { ActionIdFor } from "../Types";
import { BladeburnerActionType, BladeburnerGeneralActionName } from "@enums";
import { ActionClass, ActionParams } from "./Action";
import { clampNumber } from "../../utils/helpers/clampNumber";
import { getEnumHelper } from "../../utils/EnumHelper";
type GeneralActionParams = ActionParams & {
name: BladeburnerGeneralActionName;
@ -13,10 +14,19 @@ type GeneralActionParams = ActionParams & {
};
export class GeneralAction extends ActionClass {
type: BladeburnerActionType.General = BladeburnerActionType.General;
name: BladeburnerGeneralActionName;
get id(): ActionIdentifier {
return { type: this.type, name: this.name };
readonly type: BladeburnerActionType.General = BladeburnerActionType.General;
readonly name: BladeburnerGeneralActionName;
get id() {
return GeneralAction.createId(this.name);
}
static IsAcceptedName(name: unknown): name is BladeburnerGeneralActionName {
return getEnumHelper("BladeburnerGeneralActionName").isMember(name);
}
static createId(name: BladeburnerGeneralActionName): ActionIdFor<GeneralAction> {
return { type: BladeburnerActionType.General, name };
}
constructor(params: GeneralActionParams) {

@ -1,7 +1,7 @@
import type { Person } from "../../PersonObjects/Person";
import type { BlackOperation } from "./BlackOperation";
import type { Bladeburner } from "../Bladeburner";
import type { Availability, ActionIdentifier, SuccessChanceParams } from "../Types";
import type { ActionIdFor, Availability, SuccessChanceParams } from "../Types";
import { BladeburnerActionType, BladeburnerMultName, BladeburnerOperationName } from "@enums";
import { BladeburnerConstants } from "../data/Constants";
@ -9,6 +9,7 @@ import { ActionClass } from "./Action";
import { Generic_fromJSON, IReviverValue, constructorsForReviver } from "../../utils/JSONReviver";
import { LevelableActionClass, LevelableActionParams } from "./LevelableAction";
import { clampInteger } from "../../utils/helpers/clampNumber";
import { getEnumHelper } from "../../utils/EnumHelper";
export interface OperationParams extends LevelableActionParams {
name: BladeburnerOperationName;
@ -16,18 +17,26 @@ export interface OperationParams extends LevelableActionParams {
}
export class Operation extends LevelableActionClass {
type: BladeburnerActionType.Operation = BladeburnerActionType.Operation;
name = BladeburnerOperationName.Investigation;
readonly type: BladeburnerActionType.Operation = BladeburnerActionType.Operation;
readonly name: BladeburnerOperationName;
teamCount = 0;
get id(): ActionIdentifier {
return { type: this.type, name: this.name };
get id() {
return Operation.createId(this.name);
}
static IsAcceptedName(name: unknown): name is BladeburnerOperationName {
return getEnumHelper("BladeburnerOperationName").isMember(name);
}
static createId(name: BladeburnerOperationName): ActionIdFor<Operation> {
return { type: BladeburnerActionType.Operation, name };
}
constructor(params: OperationParams | null = null) {
super(params);
if (!params) return;
this.name = params.name;
if (params.getAvailability) this.getAvailability = params.getAvailability;
this.name = params?.name ?? BladeburnerOperationName.Investigation;
if (params && params.getAvailability) this.getAvailability = params.getAvailability;
}
// These functions are shared between operations and blackops, so they are defined outside of Operation
@ -45,6 +54,7 @@ export class Operation extends LevelableActionClass {
return 1;
}
getSuccessChance(inst: Bladeburner, person: Person, params: SuccessChanceParams) {
if (this.name === BladeburnerOperationName.Raid && inst.getCurrentCity().comms <= 0) {
return 0;

@ -1,6 +1,6 @@
import type { PromisePair } from "../Types/Promises";
import type { BlackOperation, Contract, GeneralAction, Operation } from "./Actions";
import type { ActionIdentifier, Action, Attempt } from "./Types";
import type { Action, ActionIdFor, ActionIdentifier, Attempt } from "./Types";
import type { Person } from "../PersonObjects/Person";
import type { Skills as PersonSkills } from "../PersonObjects/Skills";
@ -49,6 +49,7 @@ import { BlackOperations } from "./data/BlackOperations";
import { GeneralActions } from "./data/GeneralActions";
import { PlayerObject } from "../PersonObjects/Player/PlayerObject";
import { Sleeve } from "../PersonObjects/Sleeve/Sleeve";
import { autoCompleteTypeShorthand } from "./utils/terminalShorthands";
export const BladeburnerPromise: PromisePair<number> = { promise: null, resolve: null };
@ -433,61 +434,53 @@ export class Bladeburner {
highLow = true;
}
let actionId: ActionIdentifier;
switch (type) {
case "stamina":
// For stamina, the "name" variable is actually the stamina threshold
if (isNaN(parseFloat(name))) {
this.postToConsole("Invalid value specified for stamina threshold (must be numeric): " + name);
if (type === "stamina") {
// For stamina, the "name" variable is actually the stamina threshold
if (isNaN(parseFloat(name))) {
this.postToConsole("Invalid value specified for stamina threshold (must be numeric): " + name);
} else {
if (highLow) {
this.automateThreshHigh = Number(name);
} else {
if (highLow) {
this.automateThreshHigh = Number(name);
} else {
this.automateThreshLow = Number(name);
}
this.log("Automate (" + (highLow ? "HIGH" : "LOW") + ") stamina threshold set to " + name);
this.automateThreshLow = Number(name);
}
return;
case "general":
case "gen": {
if (!getEnumHelper("BladeburnerGeneralActionName").isMember(name)) {
this.log("Automate (" + (highLow ? "HIGH" : "LOW") + ") stamina threshold set to " + name);
}
return;
}
const actionId = autoCompleteTypeShorthand(type, name);
if (actionId === null) {
switch (type) {
case "general":
case "gen": {
this.postToConsole("Invalid General Action name specified: " + name);
return;
}
actionId = { type: BladeburnerActionType.General, name };
break;
}
case "contract":
case "contracts": {
if (!getEnumHelper("BladeburnerContractName").isMember(name)) {
case "contract":
case "contracts": {
this.postToConsole("Invalid Contract name specified: " + name);
return;
}
actionId = { type: BladeburnerActionType.Contract, name };
break;
}
case "ops":
case "op":
case "operations":
case "operation":
if (!getEnumHelper("BladeburnerOperationName").isMember(name)) {
case "ops":
case "op":
case "operations":
case "operation":
this.postToConsole("Invalid Operation name specified: " + name);
return;
}
actionId = { type: BladeburnerActionType.Operation, name };
break;
default:
this.postToConsole("Invalid use of automate command.");
return;
default:
this.postToConsole("Invalid use of automate command.");
return;
}
}
if (highLow) {
this.automateActionHigh = actionId;
} else {
this.automateActionLow = actionId;
}
this.log("Automate (" + (highLow ? "HIGH" : "LOW") + ") action set to " + name);
return;
}
}
@ -1406,10 +1399,10 @@ export class Bladeburner {
}
/** Return the action based on an ActionIdentifier, discriminating types when possible */
getActionObject(actionId: ActionIdentifier & { type: BladeburnerActionType.BlackOp }): BlackOperation;
getActionObject(actionId: ActionIdentifier & { type: BladeburnerActionType.Operation }): Operation;
getActionObject(actionId: ActionIdentifier & { type: BladeburnerActionType.Contract }): Contract;
getActionObject(actionId: ActionIdentifier & { type: BladeburnerActionType.General }): GeneralAction;
getActionObject(actionId: ActionIdFor<BlackOperation>): BlackOperation;
getActionObject(actionId: ActionIdFor<Operation>): Operation;
getActionObject(actionId: ActionIdFor<Contract>): Contract;
getActionObject(actionId: ActionIdFor<GeneralAction>): GeneralAction;
getActionObject(actionId: ActionIdentifier): Action;
getActionObject(actionId: ActionIdentifier): Action {
switch (actionId.type) {
@ -1427,36 +1420,8 @@ export class Bladeburner {
/** Fuzzy matching for action identifiers. Should be removed in 3.0 */
getActionFromTypeAndName(type: string, name: string): Action | null {
if (!type || !name) return null;
const convertedType = type.toLowerCase().trim();
switch (convertedType) {
case "contract":
case "contracts":
case "contr":
if (!getEnumHelper("BladeburnerContractName").isMember(name)) return null;
return this.contracts[name];
case "operation":
case "operations":
case "op":
case "ops":
if (!getEnumHelper("BladeburnerOperationName").isMember(name)) return null;
return this.operations[name];
case "blackoperation":
case "black operation":
case "black operations":
case "black op":
case "black ops":
case "blackop":
case "blackops":
if (!getEnumHelper("BladeburnerBlackOpName").isMember(name)) return null;
return BlackOperations[name];
case "general":
case "general action":
case "gen": {
if (!getEnumHelper("BladeburnerGeneralActionName").isMember(name)) return null;
return GeneralActions[name];
}
}
return null;
const id = autoCompleteTypeShorthand(type, name);
return id ? this.getActionObject(id) : null;
}
static keysToSave = getKeyList(Bladeburner, { removedKeys: ["skillMultipliers"] });

@ -1,11 +1,4 @@
import type { BlackOperation, Contract, GeneralAction, Operation } from "./Actions";
import type {
BladeburnerActionType,
BladeburnerBlackOpName,
BladeburnerContractName,
BladeburnerOperationName,
BladeburnerGeneralActionName,
} from "@enums";
export interface SuccessChanceParams {
/** Whether the success chance should be based on estimated statistics */
@ -21,11 +14,12 @@ type AttemptFailure = { success?: undefined; message: string };
export type Attempt<T extends object = object> = AttemptSuccess<T> | AttemptFailure;
export type Action = Contract | Operation | BlackOperation | GeneralAction;
export type ActionIdFor<ActionType extends Action> = Pick<ActionType, "type" | "name">;
export type ActionIdentifier =
| { type: BladeburnerActionType.BlackOp; name: BladeburnerBlackOpName }
| { type: BladeburnerActionType.Contract; name: BladeburnerContractName }
| { type: BladeburnerActionType.Operation; name: BladeburnerOperationName }
| { type: BladeburnerActionType.General; name: BladeburnerGeneralActionName };
| ActionIdFor<Contract>
| ActionIdFor<Operation>
| ActionIdFor<BlackOperation>
| ActionIdFor<GeneralAction>;
export type LevelableAction = Contract | Operation;

@ -0,0 +1,39 @@
import { ActionIdentifier } from "../Types";
import { BladeburnerActionType } from "@enums";
import { BlackOperation, Contract, GeneralAction, Operation } from "../Actions";
const resolveActionIdentifierFromName = (name: unknown): ActionIdentifier | null => {
if (Contract.IsAcceptedName(name)) return Contract.createId(name);
if (BlackOperation.IsAcceptedName(name)) return BlackOperation.createId(name);
if (GeneralAction.IsAcceptedName(name)) return GeneralAction.createId(name);
if (Operation.IsAcceptedName(name)) return Operation.createId(name);
return null;
};
/** Resolve identifier by auto completing from a fuzzy type match, e.g. "blackops" */
export function autoCompleteTypeShorthand(typeShorthand: string, name: string): ActionIdentifier | null {
let id = resolveActionIdentifierFromName(name);
if (id && !TerminalShorthands[id.type].includes(typeShorthand.toLowerCase().trim())) {
id = null;
}
return id;
}
/** These shorthands match those documented in the BB Terminal Help */
export const TerminalShorthands = {
[BladeburnerActionType.Contract]: <string[]>["contract", "contracts", "contr"],
[BladeburnerActionType.Operation]: <string[]>["operation", "operations", "op", "ops"],
[BladeburnerActionType.BlackOp]: <string[]>[
"blackoperation",
"black operation",
"black operations",
"black op",
"black ops",
"blackop",
"blackops",
],
[BladeburnerActionType.General]: <string[]>["general", "general action", "gen"],
} as const;

@ -70,6 +70,7 @@ export class SleeveBladeburnerWork extends SleeveWorkClass {
this.finish();
}
}
get nextCompletion(): Promise<void> {
if (!this.nextCompletionPair.promise)
this.nextCompletionPair.promise = new Promise((r) => (this.nextCompletionPair.resolve = r));

@ -12,6 +12,7 @@ export interface IReviverValue<T = any> {
ctor: string;
data: T;
}
function isReviverValue(value: unknown): value is IReviverValue {
return (
typeof value === "object" && value !== null && "ctor" in value && typeof value.ctor === "string" && "data" in value

@ -0,0 +1,38 @@
import { autoCompleteTypeShorthand, TerminalShorthands } from "../../../../src/Bladeburner/utils/terminalShorthands";
import {
BladeburnerActionType,
BladeburnerBlackOpName,
BladeburnerContractName,
BladeburnerGeneralActionName,
BladeburnerOperationName,
} from "@enums";
const ShorthandCases = (type: keyof typeof TerminalShorthands) => <string[][]>TerminalShorthands[type].map(Array);
describe("Bladeburner Actions", () => {
const EXAMPLES = [
[BladeburnerActionType.General, BladeburnerGeneralActionName.Diplomacy],
[BladeburnerActionType.BlackOp, BladeburnerBlackOpName.OperationTyphoon],
[BladeburnerActionType.Contract, BladeburnerContractName.BountyHunter],
[BladeburnerActionType.Operation, BladeburnerOperationName.Assassination],
] as const;
describe("May be described with shorthands", () => {
describe.each(EXAMPLES)("Type: %s", (type, name) => {
it.each(ShorthandCases(type))("%s", (shorthand) => {
const action = autoCompleteTypeShorthand(shorthand, name);
expect(action).toMatchObject({ type, name });
});
});
});
it("Does not match for existing action where type differs", () => {
const action = autoCompleteTypeShorthand(BladeburnerActionType.Contract, BladeburnerOperationName.Assassination);
expect(action).toBeNull();
});
it("Does not match for undocumented shorthands", () => {
const action = autoCompleteTypeShorthand("blackoperations", BladeburnerOperationName.Assassination);
expect(action).toBeNull();
});
});