mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2026-04-29 12:27:07 +02:00
API: Standardize "nextCompletion" promise in tasks (#2687)
This commit is contained in:
@@ -1,5 +1,21 @@
|
||||
import { installAugmentations } from "../../../src/Augmentation/AugmentationHelpers";
|
||||
import { AugmentationName, CompanyName, CompletedProgramName, FactionName, JobField, JobName } from "@enums";
|
||||
import {
|
||||
AugmentationName,
|
||||
BladeburnerContractName,
|
||||
BladeburnerGeneralActionName,
|
||||
CityName,
|
||||
CompanyName,
|
||||
CompletedProgramName,
|
||||
CrimeType,
|
||||
FactionName,
|
||||
FactionWorkType,
|
||||
GymType,
|
||||
JobField,
|
||||
JobName,
|
||||
LocationName,
|
||||
SpecialBladeburnerActionTypeForSleeve,
|
||||
UniversityClassType,
|
||||
} from "@enums";
|
||||
import { Player } from "@player";
|
||||
import { prestigeSourceFile } from "../../../src/Prestige";
|
||||
import { disconnectServers, GetServerOrThrow } from "../../../src/Server/AllServers";
|
||||
@@ -14,6 +30,7 @@ import { CompanyPositions } from "../../../src/Company/CompanyPositions";
|
||||
import { getTorRouter } from "../../../src/Server/ServerHelpers";
|
||||
import * as exceptionAlertModule from "../../../src/utils/helpers/exceptionAlert";
|
||||
import { numberOfBlackOperations } from "../../../src/Bladeburner/data/BlackOperations";
|
||||
import type { SleeveTask, Task } from "@nsdefs";
|
||||
|
||||
const nextBN = 4;
|
||||
|
||||
@@ -844,3 +861,304 @@ describe("Intelligence", () => {
|
||||
expectIntelligenceExp(50);
|
||||
});
|
||||
});
|
||||
|
||||
const nextCompletionTestCases = [
|
||||
{
|
||||
action: () =>
|
||||
expect(
|
||||
getNS().singularity.universityCourse(LocationName.Sector12RothmanUniversity, UniversityClassType.algorithms),
|
||||
).toStrictEqual(true),
|
||||
taskType: "CLASS",
|
||||
isPlayerTask: true,
|
||||
},
|
||||
{
|
||||
action: () =>
|
||||
expect(getNS().singularity.gymWorkout(LocationName.Sector12PowerhouseGym, GymType.strength)).toStrictEqual(true),
|
||||
taskType: "CLASS",
|
||||
isPlayerTask: true,
|
||||
},
|
||||
{
|
||||
action: () => {
|
||||
const ns = getNS();
|
||||
ns.singularity.applyToCompany(LocationName.Sector12JoesGuns, JobField.employee);
|
||||
expect(ns.singularity.workForCompany(LocationName.Sector12JoesGuns)).toStrictEqual(true);
|
||||
},
|
||||
taskType: "COMPANY",
|
||||
isPlayerTask: true,
|
||||
},
|
||||
{
|
||||
action: () => expect(getNS().singularity.createProgram(CompletedProgramName.bruteSsh)).toStrictEqual(true),
|
||||
taskType: "CREATE_PROGRAM",
|
||||
isPlayerTask: true,
|
||||
},
|
||||
{
|
||||
action: () => {
|
||||
const ns = getNS();
|
||||
ns.singularity.commitCrime(CrimeType.mug);
|
||||
expect(ns.singularity.getCurrentWork()?.type === "CRIME").toStrictEqual(true);
|
||||
},
|
||||
taskType: "CRIME",
|
||||
isPlayerTask: true,
|
||||
},
|
||||
{
|
||||
action: () =>
|
||||
expect(getNS().singularity.workForFaction(FactionName.Sector12, FactionWorkType.hacking)).toStrictEqual(true),
|
||||
taskType: "FACTION",
|
||||
isPlayerTask: true,
|
||||
},
|
||||
{
|
||||
action: () => {
|
||||
const ns = getNS();
|
||||
ns.singularity.travelToCity(CityName.NewTokyo);
|
||||
expect(ns.grafting.graftAugmentation(AugmentationName.Targeting1)).toStrictEqual(true);
|
||||
},
|
||||
taskType: "GRAFTING",
|
||||
isPlayerTask: true,
|
||||
},
|
||||
{
|
||||
action: () =>
|
||||
expect(
|
||||
getNS().sleeve.setToUniversityCourse(0, LocationName.Sector12RothmanUniversity, UniversityClassType.algorithms),
|
||||
).toStrictEqual(true),
|
||||
taskType: "CLASS",
|
||||
isPlayerTask: false,
|
||||
},
|
||||
{
|
||||
action: () =>
|
||||
expect(getNS().sleeve.setToGymWorkout(0, LocationName.Sector12PowerhouseGym, GymType.strength)).toStrictEqual(
|
||||
true,
|
||||
),
|
||||
taskType: "CLASS",
|
||||
isPlayerTask: false,
|
||||
},
|
||||
{
|
||||
action: () => {
|
||||
const ns = getNS();
|
||||
ns.singularity.applyToCompany(LocationName.Sector12JoesGuns, JobField.employee);
|
||||
expect(ns.sleeve.setToCompanyWork(0, LocationName.Sector12JoesGuns)).toStrictEqual(true);
|
||||
},
|
||||
taskType: "COMPANY",
|
||||
isPlayerTask: false,
|
||||
},
|
||||
{
|
||||
action: () => expect(getNS().sleeve.setToCommitCrime(0, CrimeType.mug)).toStrictEqual(true),
|
||||
taskType: "CRIME",
|
||||
isPlayerTask: false,
|
||||
},
|
||||
{
|
||||
action: () =>
|
||||
expect(getNS().sleeve.setToFactionWork(0, FactionName.Sector12, FactionWorkType.hacking)).toStrictEqual(true),
|
||||
taskType: "FACTION",
|
||||
isPlayerTask: false,
|
||||
},
|
||||
{
|
||||
action: () => expect(getNS().sleeve.setToShockRecovery(0)).toStrictEqual(true),
|
||||
taskType: "RECOVERY",
|
||||
isPlayerTask: false,
|
||||
},
|
||||
{
|
||||
action: () => expect(getNS().sleeve.setToSynchronize(0)).toStrictEqual(true),
|
||||
taskType: "SYNCHRO",
|
||||
isPlayerTask: false,
|
||||
},
|
||||
{
|
||||
action: () =>
|
||||
expect(getNS().sleeve.setToBladeburnerAction(0, BladeburnerGeneralActionName.Training)).toStrictEqual(true),
|
||||
taskType: "BLADEBURNER",
|
||||
isPlayerTask: false,
|
||||
},
|
||||
{
|
||||
action: () =>
|
||||
expect(
|
||||
getNS().sleeve.setToBladeburnerAction(0, SpecialBladeburnerActionTypeForSleeve.InfiltrateSynthoids),
|
||||
).toStrictEqual(true),
|
||||
taskType: "INFILTRATE",
|
||||
isPlayerTask: false,
|
||||
},
|
||||
{
|
||||
action: () =>
|
||||
expect(
|
||||
getNS().sleeve.setToBladeburnerAction(0, SpecialBladeburnerActionTypeForSleeve.SupportMainSleeve),
|
||||
).toStrictEqual(true),
|
||||
taskType: "SUPPORT",
|
||||
isPlayerTask: false,
|
||||
},
|
||||
{
|
||||
action: () =>
|
||||
expect(
|
||||
getNS().sleeve.setToBladeburnerAction(
|
||||
0,
|
||||
SpecialBladeburnerActionTypeForSleeve.TakeOnContracts,
|
||||
BladeburnerContractName.Tracking,
|
||||
),
|
||||
).toStrictEqual(true),
|
||||
taskType: "BLADEBURNER",
|
||||
isPlayerTask: false,
|
||||
},
|
||||
] as const;
|
||||
|
||||
function assertNoCurrentTask(): void {
|
||||
expect(Player.currentWork).toBeNull();
|
||||
expect(Player.sleeves[0].currentWork).toBeNull();
|
||||
}
|
||||
|
||||
async function testNextCompletion(
|
||||
action: () => void,
|
||||
taskType: Task["type"] | SleeveTask["type"],
|
||||
isPlayerTask: boolean,
|
||||
): Promise<void> {
|
||||
const ns = getNS();
|
||||
let isCompletable;
|
||||
let isRepeatable = false;
|
||||
switch (taskType) {
|
||||
case "CLASS":
|
||||
isCompletable = false;
|
||||
break;
|
||||
case "COMPANY":
|
||||
isCompletable = false;
|
||||
break;
|
||||
case "CREATE_PROGRAM":
|
||||
isCompletable = true;
|
||||
break;
|
||||
case "CRIME":
|
||||
isCompletable = true;
|
||||
isRepeatable = true;
|
||||
break;
|
||||
case "FACTION":
|
||||
isCompletable = false;
|
||||
break;
|
||||
case "GRAFTING":
|
||||
isCompletable = true;
|
||||
break;
|
||||
case "BLADEBURNER":
|
||||
isCompletable = true;
|
||||
isRepeatable = true;
|
||||
break;
|
||||
case "INFILTRATE":
|
||||
isCompletable = true;
|
||||
isRepeatable = true;
|
||||
break;
|
||||
case "RECOVERY":
|
||||
isCompletable = false;
|
||||
break;
|
||||
case "SUPPORT":
|
||||
isCompletable = false;
|
||||
break;
|
||||
case "SYNCHRO":
|
||||
isCompletable = false;
|
||||
break;
|
||||
default: {
|
||||
// Verify type switch statement is exhaustive
|
||||
const __a: never = taskType;
|
||||
throw new Error(`Invalid taskType: ${taskType}`);
|
||||
}
|
||||
}
|
||||
|
||||
const processTask = async (cycles: number) => {
|
||||
if (isPlayerTask) {
|
||||
Player.processWork(cycles);
|
||||
} else {
|
||||
Player.sleeves[0].currentWork?.process(Player.sleeves[0], cycles);
|
||||
}
|
||||
// Yield to the microtask queue.
|
||||
await Promise.resolve();
|
||||
};
|
||||
|
||||
const cancelTask = async () => {
|
||||
if (isPlayerTask) {
|
||||
ns.singularity.stopAction();
|
||||
} else {
|
||||
ns.sleeve.setToIdle(0);
|
||||
}
|
||||
// Yield to the microtask queue.
|
||||
await Promise.resolve();
|
||||
};
|
||||
|
||||
const assertCurrentTask = () => {
|
||||
if (isPlayerTask) {
|
||||
expect(Player.currentWork).not.toBeNull();
|
||||
} else {
|
||||
expect(Player.sleeves[0].currentWork).not.toBeNull();
|
||||
}
|
||||
};
|
||||
|
||||
let isResolved = false;
|
||||
const setUpNextCompletionPromise = () => {
|
||||
if (isPlayerTask) {
|
||||
void ns.singularity.getCurrentWork()?.nextCompletion.then(() => (isResolved = true));
|
||||
} else {
|
||||
void ns.sleeve.getTask(0)?.nextCompletion.then(() => (isResolved = true));
|
||||
}
|
||||
};
|
||||
|
||||
assertNoCurrentTask();
|
||||
action();
|
||||
setUpNextCompletionPromise();
|
||||
expect(isResolved).toStrictEqual(false);
|
||||
|
||||
// The current task should remain incomplete after 1 cycle.
|
||||
await processTask(1);
|
||||
assertCurrentTask();
|
||||
expect(isResolved).toStrictEqual(false);
|
||||
|
||||
// Run many cycles to ensure all completable tasks are completed.
|
||||
await processTask(1e4);
|
||||
|
||||
if (isCompletable) {
|
||||
// The nextCompletion promise should be resolved now.
|
||||
expect(isResolved).toStrictEqual(true);
|
||||
if (isRepeatable) {
|
||||
assertCurrentTask();
|
||||
// Create the promise again to verify cancellation.
|
||||
isResolved = false;
|
||||
setUpNextCompletionPromise();
|
||||
} else {
|
||||
assertNoCurrentTask();
|
||||
// Run the action again. We will cancel it later to verify cancellation.
|
||||
if (taskType !== "GRAFTING") {
|
||||
// Delete the completed program before creating it again.
|
||||
if (taskType === "CREATE_PROGRAM") {
|
||||
ns.rm(CompletedProgramName.bruteSsh);
|
||||
}
|
||||
action();
|
||||
} else {
|
||||
// Graft a different augmentation.
|
||||
ns.grafting.graftAugmentation(AugmentationName.BitWire);
|
||||
}
|
||||
// Create the promise again to verify cancellation.
|
||||
isResolved = false;
|
||||
setUpNextCompletionPromise();
|
||||
}
|
||||
}
|
||||
expect(isResolved).toStrictEqual(false);
|
||||
// Verify cancellation.
|
||||
await cancelTask();
|
||||
assertNoCurrentTask();
|
||||
expect(isResolved).toStrictEqual(true);
|
||||
}
|
||||
|
||||
describe("nextCompletion", () => {
|
||||
beforeEach(() => {
|
||||
setupBasicTestingEnvironment();
|
||||
Player.sourceFiles.set(7, 3);
|
||||
Player.sourceFiles.set(10, 3);
|
||||
prestigeSourceFile(true);
|
||||
Player.money = 1e15;
|
||||
gainTonsOfExp();
|
||||
const ns = getNS();
|
||||
expect(ns.bladeburner.joinBladeburnerDivision()).toStrictEqual(true);
|
||||
if (!Player.bladeburner) {
|
||||
throw new Error("Bladeburner was not initialized");
|
||||
}
|
||||
Player.bladeburner.contracts[BladeburnerContractName.Tracking].count = 1e6;
|
||||
ns.singularity.checkFactionInvitations();
|
||||
expect(ns.singularity.joinFaction(FactionName.Sector12)).toStrictEqual(true);
|
||||
ns.sleeve.setToIdle(0);
|
||||
});
|
||||
test.each(nextCompletionTestCases)(
|
||||
"Task type: $taskType - Is player task: $isPlayerTask",
|
||||
async ({ action, taskType, isPlayerTask }) => {
|
||||
await testNextCompletion(action, taskType, isPlayerTask);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user