mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2026-04-16 06:18:42 +02:00
MISC: Rework intelligence override (#2575)
This commit is contained in:
@@ -1,46 +1,34 @@
|
||||
import { Player } from "@player";
|
||||
import fs from "node:fs";
|
||||
import type { ScriptFilePath } from "../../../src/Paths/ScriptFilePath";
|
||||
import { loadGame } from "../../../src/SaveObject";
|
||||
import { loadGame, saveObject } from "../../../src/SaveObject";
|
||||
import * as db from "../../../src/db";
|
||||
import * as FileUtils from "../../../src/utils/FileUtils";
|
||||
import type { SaveData } from "../../../src/types";
|
||||
import { calculateExp } from "../../../src/PersonObjects/formulas/skill";
|
||||
|
||||
async function loadGameFromSaveData(saveData: SaveData) {
|
||||
// Simulate loading the data in IndexedDB
|
||||
const mockedLoad = jest.spyOn(db, "load");
|
||||
// We must use structuredClone(saveData) instead of saveData; otherwise, the check of mockedDownload won't catch wrong
|
||||
// changes in evaluateVersionCompatibility (e.g., unexpectedly mutating saveData before passing it to
|
||||
// downloadContentAsFile).
|
||||
mockedLoad.mockReturnValue(Promise.resolve(structuredClone(saveData)));
|
||||
|
||||
// Simulate saving the data in IndexedDB
|
||||
jest.spyOn(db, "save").mockImplementation(() => Promise.resolve());
|
||||
|
||||
const mockedDownload = jest.spyOn(FileUtils, "downloadContentAsFile").mockImplementation(() => {});
|
||||
|
||||
await loadGame(saveData);
|
||||
|
||||
return mockedDownload;
|
||||
}
|
||||
|
||||
describe("v3", () => {
|
||||
test("v2.8.1 to v3.0.0", async () => {
|
||||
const saveData = new Uint8Array(fs.readFileSync("test/jest/Migration/save-files/v2.8.1.gz"));
|
||||
|
||||
// Simulate loading the data in IndexedDB
|
||||
const mockedLoad = jest.spyOn(db, "load");
|
||||
/**
|
||||
* We must use structuredClone(saveData) instead of saveData; otherwise, the check of mockedDownload won't catch
|
||||
* wrong changes in evaluateVersionCompatibility (e.g., unexpectedly mutating saveData before passing it to
|
||||
* downloadContentAsFile).
|
||||
*/
|
||||
mockedLoad.mockReturnValue(Promise.resolve(structuredClone(saveData)));
|
||||
|
||||
const mockedDownload = jest.spyOn(FileUtils, "downloadContentAsFile");
|
||||
|
||||
const originalConsoleError = console.error;
|
||||
const originalConsoleWarning = console.warn;
|
||||
const consoleError = jest.spyOn(console, "error").mockImplementation((...data: unknown[]) => {
|
||||
if (Array.isArray(data) && data.length > 0 && (data[0] === "There was no Darknet savedata" || data[0] === "")) {
|
||||
return;
|
||||
}
|
||||
originalConsoleError(...data);
|
||||
});
|
||||
const consoleWarning = jest.spyOn(console, "warn").mockImplementation((...data: unknown[]) => {
|
||||
if (
|
||||
Array.isArray(data) &&
|
||||
data.length > 0 &&
|
||||
(data[0] === "Encountered the following issue while loading Darknet savedata:" || data[0] === "Savedata:")
|
||||
) {
|
||||
return;
|
||||
}
|
||||
originalConsoleWarning(...data);
|
||||
});
|
||||
await loadGame(await db.load());
|
||||
consoleError.mockRestore();
|
||||
consoleWarning.mockRestore();
|
||||
const mockedDownload = await loadGameFromSaveData(saveData);
|
||||
|
||||
// Check if auto-migration works
|
||||
expect(
|
||||
@@ -57,4 +45,64 @@ describe("v3", () => {
|
||||
// Check if evaluateVersionCompatibility correctly loads the data in IndexedDB and passes it to downloadContentAsFile
|
||||
expect(mockedDownload).toHaveBeenCalledWith(saveData, "bitburnerSave_backup_2.8.1_1756913326.json.gz");
|
||||
});
|
||||
|
||||
test.each([
|
||||
["test/jest/Migration/save-files/v2.8.1_500int.gz", 1773597870229, 1773597871370, undefined, 500, 300],
|
||||
["test/jest/Migration/save-files/v2.8.1_500int_override_100int.gz", 1773597926723, 1773597928370, 100, 500, 300],
|
||||
["test/jest/Migration/save-files/v2.8.1_500int_override_1000int.gz", 1773597951205, 1773597953370, 1000, 500, 300],
|
||||
])("%s", async (path, lastSave, lastUpdate, intelligenceOverride, playerInt, sleeveInt) => {
|
||||
const saveData = new Uint8Array(fs.readFileSync(path));
|
||||
const mockedDownload = await loadGameFromSaveData(saveData);
|
||||
expect(Player.lastSave).toStrictEqual(lastSave);
|
||||
|
||||
const playerIntExp =
|
||||
intelligenceOverride !== undefined && intelligenceOverride < playerInt
|
||||
? calculateExp(intelligenceOverride, 1)
|
||||
: calculateExp(playerInt, 1);
|
||||
const sleeveIntExp =
|
||||
intelligenceOverride !== undefined && intelligenceOverride < sleeveInt
|
||||
? calculateExp(intelligenceOverride, 1)
|
||||
: calculateExp(sleeveInt, 1);
|
||||
const persistentPlayerIntExp = calculateExp(playerInt, 1);
|
||||
const persistentSleeveIntExp = calculateExp(sleeveInt, 1);
|
||||
|
||||
// Check if Player.exp.intelligence and Player.persistentIntelligenceData.exp are migrated.
|
||||
expect(Player.bitNodeOptions.intelligenceOverride).toStrictEqual(intelligenceOverride);
|
||||
expect(Player.exp.intelligence).toStrictEqual(playerIntExp);
|
||||
expect(Player.persistentIntelligenceData.exp).toStrictEqual(persistentPlayerIntExp);
|
||||
for (const sleeve of Player.sleeves) {
|
||||
expect(sleeve.exp.intelligence).toStrictEqual(sleeveIntExp);
|
||||
expect(sleeve.persistentIntelligenceData.exp).toStrictEqual(persistentSleeveIntExp);
|
||||
}
|
||||
|
||||
const expGain = 1e9;
|
||||
// Gain exp and check if it is accumulated correctly.
|
||||
Player.gainIntelligenceExp(expGain);
|
||||
expect(Player.exp.intelligence).toStrictEqual(playerIntExp + expGain);
|
||||
expect(Player.persistentIntelligenceData.exp).toStrictEqual(persistentPlayerIntExp + expGain);
|
||||
for (const sleeve of Player.sleeves) {
|
||||
sleeve.gainIntelligenceExp(expGain);
|
||||
expect(sleeve.exp.intelligence).toStrictEqual(sleeveIntExp + expGain);
|
||||
expect(sleeve.persistentIntelligenceData.exp).toStrictEqual(persistentSleeveIntExp + expGain);
|
||||
}
|
||||
|
||||
// Save and reload.
|
||||
await saveObject.saveGame();
|
||||
await loadGameFromSaveData(await saveObject.getSaveData());
|
||||
expect(Player.lastSave).not.toStrictEqual(lastSave);
|
||||
|
||||
// Check if gained exp is saved correctly.
|
||||
expect(Player.bitNodeOptions.intelligenceOverride).toStrictEqual(intelligenceOverride);
|
||||
expect(Player.exp.intelligence).toStrictEqual(playerIntExp + expGain);
|
||||
expect(Player.persistentIntelligenceData.exp).toStrictEqual(persistentPlayerIntExp + expGain);
|
||||
for (const sleeve of Player.sleeves) {
|
||||
expect(sleeve.exp.intelligence).toStrictEqual(sleeveIntExp + expGain);
|
||||
expect(sleeve.persistentIntelligenceData.exp).toStrictEqual(persistentSleeveIntExp + expGain);
|
||||
}
|
||||
|
||||
expect(mockedDownload).toHaveBeenCalledWith(
|
||||
saveData,
|
||||
`bitburnerSave_backup_2.8.1_${Math.round(lastUpdate / 1000)}.json.gz`,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
BIN
test/jest/Migration/save-files/v2.8.1_500int.gz
Normal file
BIN
test/jest/Migration/save-files/v2.8.1_500int.gz
Normal file
Binary file not shown.
BIN
test/jest/Migration/save-files/v2.8.1_500int_override_1000int.gz
Normal file
BIN
test/jest/Migration/save-files/v2.8.1_500int_override_1000int.gz
Normal file
Binary file not shown.
BIN
test/jest/Migration/save-files/v2.8.1_500int_override_100int.gz
Normal file
BIN
test/jest/Migration/save-files/v2.8.1_500int_override_100int.gz
Normal file
Binary file not shown.
@@ -15,7 +15,7 @@ import { CompanyPositions } from "../../../src/Company/CompanyPositions";
|
||||
import { getTorRouter } from "../../../src/Server/ServerHelpers";
|
||||
import * as exceptionAlertModule from "../../../src/utils/helpers/exceptionAlert";
|
||||
|
||||
const nextBN = 3;
|
||||
const nextBN = 4;
|
||||
|
||||
function setNumBlackOpsComplete(value: number): void {
|
||||
if (!Player.bladeburner) {
|
||||
@@ -48,19 +48,139 @@ beforeAll(() => {
|
||||
initGameEnvironment();
|
||||
});
|
||||
|
||||
function testIntelligenceOverride(
|
||||
ns: NSFull,
|
||||
prestigeAPI: "b1tflum3" | "destroyW0r1dD43m0n",
|
||||
expectSuccessPrestige: () => void,
|
||||
setUpBeforePrestige = () => {},
|
||||
): void {
|
||||
Player.sourceFiles.set(5, 1);
|
||||
// Start without exp.
|
||||
expect(Player.exp.intelligence).toStrictEqual(0);
|
||||
expect(Player.skills.intelligence).toStrictEqual(1);
|
||||
expect(Player.persistentIntelligenceData.exp).toStrictEqual(0);
|
||||
// Gain 1e6 exp (skill = 242).
|
||||
Player.gainIntelligenceExp(1e6);
|
||||
expect(Player.exp.intelligence).toStrictEqual(1e6);
|
||||
expect(Player.skills.intelligence).toStrictEqual(242);
|
||||
expect(Player.persistentIntelligenceData.exp).toStrictEqual(1e6);
|
||||
|
||||
// Prestige and check if intelligenceOverride works (exp is set to 11255, skill = 100, and
|
||||
// persistentIntelligenceData.exp is still 1e6).
|
||||
const intelligenceExpGainOnPrestige = prestigeAPI === "destroyW0r1dD43m0n" ? 300 : 0;
|
||||
setUpBeforePrestige();
|
||||
ns.singularity[prestigeAPI](nextBN, undefined, {
|
||||
...ns.getResetInfo().bitNodeOptions,
|
||||
intelligenceOverride: 100,
|
||||
});
|
||||
expectSuccessPrestige();
|
||||
expect(Player.bitNodeOptions.intelligenceOverride).toStrictEqual(100);
|
||||
expect(Player.exp.intelligence).toStrictEqual(11255.317546552918 + intelligenceExpGainOnPrestige);
|
||||
expect(Player.skills.intelligence).toStrictEqual(100);
|
||||
expect(Player.persistentIntelligenceData.exp).toStrictEqual(1e6 + intelligenceExpGainOnPrestige);
|
||||
|
||||
// Gain 500e3 exp.
|
||||
const intExpGain = 500e3;
|
||||
Player.gainIntelligenceExp(intExpGain);
|
||||
// Check if int gain is accumulated correctly in both Player.exp.intelligence and
|
||||
// Player.persistentIntelligenceData.exp.
|
||||
expect(Player.exp.intelligence).toStrictEqual(11255.317546552918 + intelligenceExpGainOnPrestige + intExpGain);
|
||||
expect(Player.skills.intelligence).toStrictEqual(220);
|
||||
expect(Player.persistentIntelligenceData.exp).toStrictEqual(1e6 + intelligenceExpGainOnPrestige + intExpGain);
|
||||
|
||||
// Prestige and check if int gain is still retained correctly.
|
||||
setUpBeforePrestige();
|
||||
ns.singularity[prestigeAPI](nextBN, undefined, {
|
||||
...ns.getResetInfo().bitNodeOptions,
|
||||
intelligenceOverride: undefined,
|
||||
});
|
||||
expectSuccessPrestige();
|
||||
expect(Player.bitNodeOptions.intelligenceOverride).toStrictEqual(undefined);
|
||||
expect(Player.exp.intelligence).toStrictEqual(1e6 + intelligenceExpGainOnPrestige * 2 + intExpGain);
|
||||
expect(Player.skills.intelligence).toStrictEqual(255);
|
||||
expect(Player.persistentIntelligenceData.exp).toStrictEqual(1e6 + intelligenceExpGainOnPrestige * 2 + intExpGain);
|
||||
|
||||
// Prestige with intelligenceOverride set higher than the persistent int skill and check if the int skill is
|
||||
// incorrectly set to that value.
|
||||
setUpBeforePrestige();
|
||||
ns.singularity[prestigeAPI](nextBN, undefined, {
|
||||
...ns.getResetInfo().bitNodeOptions,
|
||||
intelligenceOverride: 1000,
|
||||
});
|
||||
expectSuccessPrestige();
|
||||
expect(Player.bitNodeOptions.intelligenceOverride).toStrictEqual(1000);
|
||||
expect(Player.exp.intelligence).toStrictEqual(1e6 + intelligenceExpGainOnPrestige * 3 + intExpGain);
|
||||
expect(Player.skills.intelligence).toStrictEqual(255);
|
||||
expect(Player.persistentIntelligenceData.exp).toStrictEqual(1e6 + intelligenceExpGainOnPrestige * 3 + intExpGain);
|
||||
|
||||
// Start testing another scenario.
|
||||
// Set the initial state (int exp = 1e6, skill = 242) and bitflume.
|
||||
Player.exp.intelligence = 1e6;
|
||||
Player.skills.intelligence = 242;
|
||||
Player.persistentIntelligenceData.exp = 1e6;
|
||||
ns.singularity.b1tflum3(nextBN, undefined, {
|
||||
...ns.getResetInfo().bitNodeOptions,
|
||||
intelligenceOverride: undefined,
|
||||
});
|
||||
|
||||
// Double-check the initial state.
|
||||
expect(Player.exp.intelligence).toStrictEqual(1e6);
|
||||
expect(Player.skills.intelligence).toStrictEqual(242);
|
||||
expect(Player.persistentIntelligenceData.exp).toStrictEqual(1e6);
|
||||
expect(Player.bitNodeOptions.intelligenceOverride).toStrictEqual(undefined);
|
||||
|
||||
// Limit int skill to 100.
|
||||
setUpBeforePrestige();
|
||||
ns.singularity[prestigeAPI](nextBN, undefined, {
|
||||
...ns.getResetInfo().bitNodeOptions,
|
||||
intelligenceOverride: 100,
|
||||
});
|
||||
expectSuccessPrestige();
|
||||
|
||||
// Check if int is overridden correctly.
|
||||
expect(Player.bitNodeOptions.intelligenceOverride).toStrictEqual(100);
|
||||
expect(Player.exp.intelligence).toStrictEqual(11255.317546552918 + intelligenceExpGainOnPrestige);
|
||||
expect(Player.skills.intelligence).toStrictEqual(100);
|
||||
expect(Player.persistentIntelligenceData.exp).toStrictEqual(1e6 + intelligenceExpGainOnPrestige);
|
||||
|
||||
// Limit int skill to 1000.
|
||||
setUpBeforePrestige();
|
||||
ns.singularity[prestigeAPI](nextBN, undefined, {
|
||||
...ns.getResetInfo().bitNodeOptions,
|
||||
intelligenceOverride: 1000,
|
||||
});
|
||||
expectSuccessPrestige();
|
||||
|
||||
// The limit is higher than the persistent int skill, so it's not applied. Exp and skill are reset back to the initial
|
||||
// state, plus the int exp gained from prestige.
|
||||
expect(Player.bitNodeOptions.intelligenceOverride).toStrictEqual(1000);
|
||||
expect(Player.exp.intelligence).toStrictEqual(1e6 + intelligenceExpGainOnPrestige * 2);
|
||||
expect(Player.skills.intelligence).toStrictEqual(242);
|
||||
expect(Player.persistentIntelligenceData.exp).toStrictEqual(1e6 + intelligenceExpGainOnPrestige * 2);
|
||||
}
|
||||
|
||||
function setUpBeforeDestroyingWD(): void {
|
||||
Player.queueAugmentation(AugmentationName.TheRedPill);
|
||||
installAugmentations();
|
||||
Player.gainHackingExp(1e100);
|
||||
const wdServer = GetServerOrThrow(SpecialServers.WorldDaemon);
|
||||
wdServer.hasAdminRights = true;
|
||||
Player.startBladeburner();
|
||||
setNumBlackOpsComplete(blackOpsArray.length);
|
||||
}
|
||||
|
||||
describe("b1tflum3", () => {
|
||||
beforeEach(() => {
|
||||
setupBasicTestingEnvironment();
|
||||
Player.queueAugmentation(AugmentationName.TheRedPill);
|
||||
Player.queueAugmentation(AugmentationName.Targeting1);
|
||||
installAugmentations();
|
||||
Player.gainHackingExp(1e100);
|
||||
const wdServer = GetServerOrThrow(SpecialServers.WorldDaemon);
|
||||
wdServer.hasAdminRights = true;
|
||||
});
|
||||
// Make sure that the player is in the next BN without SF rewards.
|
||||
const expectSucceedInB1tflum3 = () => {
|
||||
expect(Player.bitNodeN).toStrictEqual(nextBN);
|
||||
expect(Player.augmentations.length).toStrictEqual(0);
|
||||
expect(Player.exp.hacking).toStrictEqual(0);
|
||||
expect(Player.sourceFileLvl(1)).toStrictEqual(0);
|
||||
};
|
||||
|
||||
@@ -79,15 +199,20 @@ describe("b1tflum3", () => {
|
||||
});
|
||||
expectSucceedInB1tflum3();
|
||||
});
|
||||
test("intelligenceOverride", () => {
|
||||
testIntelligenceOverride(getNS(), "b1tflum3", expectSucceedInB1tflum3);
|
||||
});
|
||||
});
|
||||
|
||||
// Make sure that the player is still in the same BN without SF rewards.
|
||||
const expectFailToB1tflum3 = () => {
|
||||
expect(Player.bitNodeN).toStrictEqual(1);
|
||||
expect(Player.augmentations.length).toStrictEqual(1);
|
||||
expect(Player.augmentations[0].name).toStrictEqual(AugmentationName.Targeting1);
|
||||
expect(Player.exp.hacking).toStrictEqual(1e100);
|
||||
expect(Player.sourceFileLvl(1)).toStrictEqual(0);
|
||||
};
|
||||
describe("Failure", () => {
|
||||
// Make sure that the player is still in the same BN without SF rewards.
|
||||
const expectFailToB1tflum3 = () => {
|
||||
expect(Player.bitNodeN).toStrictEqual(1);
|
||||
expect(Player.augmentations.length).toStrictEqual(1);
|
||||
expect(Player.sourceFileLvl(1)).toStrictEqual(0);
|
||||
};
|
||||
test("Invalid intelligenceOverride", () => {
|
||||
const ns = getNS();
|
||||
expect(() => {
|
||||
@@ -114,13 +239,7 @@ describe("b1tflum3", () => {
|
||||
describe("destroyW0r1dD43m0n", () => {
|
||||
beforeEach(() => {
|
||||
setupBasicTestingEnvironment();
|
||||
Player.queueAugmentation(AugmentationName.TheRedPill);
|
||||
installAugmentations();
|
||||
Player.gainHackingExp(1e100);
|
||||
const wdServer = GetServerOrThrow(SpecialServers.WorldDaemon);
|
||||
wdServer.hasAdminRights = true;
|
||||
Player.startBladeburner();
|
||||
setNumBlackOpsComplete(blackOpsArray.length);
|
||||
setUpBeforeDestroyingWD();
|
||||
});
|
||||
|
||||
describe("Success", () => {
|
||||
@@ -162,6 +281,9 @@ describe("destroyW0r1dD43m0n", () => {
|
||||
});
|
||||
expectSucceedInDestroyingWD();
|
||||
});
|
||||
test("intelligenceOverride", () => {
|
||||
testIntelligenceOverride(getNS(), "destroyW0r1dD43m0n", expectSucceedInDestroyingWD, setUpBeforeDestroyingWD);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Failure", () => {
|
||||
@@ -488,6 +610,7 @@ describe("purchaseProgram", () => {
|
||||
const spiedExceptionAlert = jest.spyOn(exceptionAlertModule, "exceptionAlert");
|
||||
const ns = getNS();
|
||||
expect(Player.hasProgram(CompletedProgramName.darkscape)).toStrictEqual(false);
|
||||
// @ts-expect-error - Intentionally use lowercase program name
|
||||
expect(ns.singularity.purchaseProgram(CompletedProgramName.darkscape.toLowerCase())).toStrictEqual(true);
|
||||
expect(Player.hasProgram(CompletedProgramName.darkscape)).toStrictEqual(true);
|
||||
expect(spiedExceptionAlert).not.toHaveBeenCalled();
|
||||
@@ -507,6 +630,7 @@ describe("purchaseProgram", () => {
|
||||
});
|
||||
test("Invalid program name", () => {
|
||||
const ns = getNS();
|
||||
// @ts-expect-error - Intentionally use invalid program name
|
||||
expect(ns.singularity.purchaseProgram("InvalidProgram.exe")).toStrictEqual(false);
|
||||
});
|
||||
test("Not enough money", () => {
|
||||
|
||||
@@ -534,6 +534,9 @@ exports[`Check Save File Continuity PlayerSave continuity 1`] = `
|
||||
"work_money": 1,
|
||||
},
|
||||
"numPeopleKilled": 0,
|
||||
"persistentIntelligenceData": {
|
||||
"exp": 0,
|
||||
},
|
||||
"playtimeSinceLastAug": 0,
|
||||
"playtimeSinceLastBitnode": 0,
|
||||
"purchasedServers": [],
|
||||
@@ -614,6 +617,9 @@ exports[`Check Save File Continuity PlayerSave continuity 1`] = `
|
||||
"strength_exp": 1,
|
||||
"work_money": 1,
|
||||
},
|
||||
"persistentIntelligenceData": {
|
||||
"exp": 0,
|
||||
},
|
||||
"queuedAugmentations": [],
|
||||
"shock": 100,
|
||||
"skills": {
|
||||
@@ -686,6 +692,9 @@ exports[`Check Save File Continuity PlayerSave continuity 1`] = `
|
||||
"strength_exp": 1,
|
||||
"work_money": 1,
|
||||
},
|
||||
"persistentIntelligenceData": {
|
||||
"exp": 0,
|
||||
},
|
||||
"queuedAugmentations": [],
|
||||
"shock": 100,
|
||||
"skills": {
|
||||
|
||||
Reference in New Issue
Block a user