MISC: Rework intelligence override (#2575)

This commit is contained in:
catloversg
2026-03-20 10:09:37 +07:00
committed by GitHub
parent 6a9abd9544
commit 3813d03fb6
16 changed files with 295 additions and 77 deletions

View File

@@ -289,26 +289,35 @@ function IntelligenceOverride({
tooltip={
<>
<Typography component="div">
The Intelligence bonuses for you and your Sleeves will be limited by this value. For example:
Your intelligence and your Sleeves' intelligence will be temporarily set to this value if it is lower than
their current values. For example:
<ul>
<li>
If your Intelligence is 1000 and you set this value to 500, the "effective" Intelligence, which is used
for bonus calculation, is only 500.
If your intelligence is 1000 and you set this value to 500, your intelligence will be temporarily set to
500.
</li>
<li>
If a Sleeve's Intelligence is 200 and you set this value to 500, the "effective" Intelligence, which is
used for bonus calculation, is still 200.
If a Sleeve's intelligence is 200 and you set this value to 500, that Sleeve's intelligence is still
200.
</li>
</ul>
</Typography>
<Typography>
You will still gain Intelligence experience as normal. Intelligence Override only affects the Intelligence
bonus.
Note that you still gain intelligence experience as normal.
<br />
For example, suppose you have 1e6 intelligence exp (intelligence skill = 242) and set the intelligence
override to 100. At the start of the BitNode, your intelligence skill will be set to 100 (equivalent to
~11255.318 intelligence exp).
<br />
If you gain 500e3 intelligence exp during the BitNode, your intelligence skill will increase to 220 (total
intelligence exp = 11255 + 500e3 = 511255). After performing bitflume, the exp gained during the BitNode is
added to your original exp. Your intelligence skill will then become 255 (total intelligence exp = 1e6 +
500e3 = 1.5e6).
</Typography>
<br />
<Typography>
The "effective" Intelligence will be shown in the character overview. If the effective value is different
from the original value, you can hover your mouse over it to see the original value.
The overridden intelligence will be shown in the character overview. You can hover your mouse over it to see
the original value.
</Typography>
</>
}

View File

@@ -7,7 +7,7 @@ export const CONSTANTS = {
VersionString: "3.0.0dev",
isDevBranch: true,
isInTestEnvironment: globalThis.process?.env?.JEST_WORKER_ID !== undefined,
VersionNumber: 46,
VersionNumber: 47,
/** Max level for any skill, assuming no multipliers. Determined by max numerical value in javascript for experience
* and the skill level formula in Player.js. Note that all this means it that when experience hits MAX_INT, then

View File

@@ -76,6 +76,7 @@ function setStatLevel(stat: string, level: number): void {
break;
case "Intelligence":
Player.exp.intelligence = 0;
Player.persistentIntelligenceData.exp = 0;
Player.gainIntelligenceExp(calculateExp(level, 1));
break;
}
@@ -107,6 +108,7 @@ function resetAllExp(): void {
Player.exp.agility = 0;
Player.exp.charisma = 0;
Player.exp.intelligence = 0;
Player.persistentIntelligenceData.exp = 0;
Player.updateSkillLevels();
}
@@ -133,6 +135,7 @@ function resetExperience(stat: string): () => void {
break;
case "Intelligence":
Player.exp.intelligence = 0;
Player.persistentIntelligenceData.exp = 0;
break;
}
Player.updateSkillLevels();
@@ -155,6 +158,7 @@ function enableIntelligence(): void {
function disableIntelligence(): void {
Player.exp.intelligence = 0;
Player.skills.intelligence = 0;
Player.persistentIntelligenceData.exp = 0;
Player.updateSkillLevels();
}

View File

@@ -10,7 +10,7 @@ import { currentNodeMults } from "../BitNode/BitNodeMultipliers";
import { CONSTANTS } from "../Constants";
import { Player } from "../Player";
import { defaultMultipliers } from "./Multipliers";
import { calculateSkill } from "./formulas/skill";
import { calculateExp, calculateSkill } from "./formulas/skill";
// Base class representing a person-like object
export abstract class Person implements IPerson {
@@ -34,6 +34,10 @@ export abstract class Person implements IPerson {
intelligence: 0,
};
persistentIntelligenceData = {
exp: 0,
};
mults = defaultMultipliers();
/** Augmentations */
@@ -142,6 +146,23 @@ export abstract class Person implements IPerson {
);
}
overrideIntelligence(): void {
const persistentIntelligenceSkill = this.calculateSkill(this.persistentIntelligenceData.exp, 1);
// Reset exp and skill to the persistent values if there is no limit (intelligenceOverride) or the limit is greater
// than or equal to the persistent skill.
if (
Player.bitNodeOptions.intelligenceOverride === undefined ||
Player.bitNodeOptions.intelligenceOverride >= persistentIntelligenceSkill
) {
this.exp.intelligence = this.persistentIntelligenceData.exp;
this.skills.intelligence = persistentIntelligenceSkill;
return;
}
// Limit exp and skill based on intelligenceOverride only if it's smaller than the persistent skill.
this.exp.intelligence = calculateExp(Player.bitNodeOptions.intelligenceOverride, 1);
this.skills.intelligence = Player.bitNodeOptions.intelligenceOverride;
}
gainIntelligenceExp(exp: number): void {
if (isNaN(exp)) {
console.error("ERROR: NaN passed into Player.gainIntelligenceExp()");
@@ -154,6 +175,7 @@ export abstract class Person implements IPerson {
if (Player.sourceFileLvl(5) > 0 || this.skills.intelligence > 0 || Player.bitNodeN === 5) {
this.exp.intelligence += exp;
this.skills.intelligence = Math.floor(this.calculateSkill(this.exp.intelligence, 1));
this.persistentIntelligenceData.exp += exp;
}
}

View File

@@ -140,6 +140,7 @@ export function prestigeAugmentation(this: PlayerObject): void {
export function prestigeSourceFile(this: PlayerObject): void {
this.entropy = 0;
this.prestigeAugmentation();
this.overrideIntelligence();
this.karma = 0;
// Duplicate sleeves are reset to level 1 every Bit Node (but the number of sleeves you have persists)
this.sleeves.forEach((sleeve) => sleeve.prestige());

View File

@@ -251,6 +251,8 @@ export class Sleeve extends Person implements SleevePerson {
this.shock = 100;
this.storedCycles = 0;
this.sync = Math.max(this.memory, 1);
this.overrideIntelligence();
}
/**

View File

@@ -1,9 +1,3 @@
import { Player } from "@player";
export function calculateIntelligenceBonus(intelligence: number, weight = 1): number {
const effectiveIntelligence =
Player.bitNodeOptions.intelligenceOverride !== undefined
? Math.min(Player.bitNodeOptions.intelligenceOverride, intelligence)
: intelligence;
return 1 + (weight * Math.pow(effectiveIntelligence, 0.8)) / 600;
return 1 + (weight * Math.pow(intelligence, 0.8)) / 600;
}

View File

@@ -66,6 +66,7 @@ export function enterBitNode(
} else if (Player.sourceFileLvl(5) === 0 && newBitNode !== 5) {
Player.skills.intelligence = 0;
Player.exp.intelligence = 0;
Player.persistentIntelligenceData.exp = 0;
}
if (newBitNode === 5 && Player.skills.intelligence === 0) {
Player.skills.intelligence = 1;

View File

@@ -71,15 +71,15 @@ export function Val({ name, color }: ValProps): React.ReactElement {
return clearSubscription;
}, [name]);
if (
name === "Int" &&
Player.bitNodeOptions.intelligenceOverride !== undefined &&
Player.bitNodeOptions.intelligenceOverride < Player.skills.intelligence
) {
if (name === "Int" && Player.bitNodeOptions.intelligenceOverride !== undefined) {
return (
<Tooltip title={`Intelligence: ${formatSkill(Player.skills.intelligence)}`}>
<Tooltip
title={`Persistent Intelligence: ${formatSkill(
Player.calculateSkill(Player.persistentIntelligenceData.exp, 1),
)}`}
>
<Typography color={color}>
{formatSkill(Player.bitNodeOptions.intelligenceOverride)}
{formatSkill(Player.skills.intelligence)}
<sup>*</sup>
</Typography>
</Tooltip>
@@ -140,7 +140,7 @@ export function CharacterOverview({ parentOpen, save, killScripts }: OverviewPro
const theme = useTheme();
return (
<>
<Table sx={{ display: "block", m: 1 }}>
<Table sx={{ display: "block", p: 1 }}>
<TableBody>
<DataRow name="HP" showBar={false} color={theme.colors.hp} cellType={"cellNone"} />
<DataRow name="Money" showBar={false} color={theme.colors.money} cellType={"cell"} />

View File

@@ -625,6 +625,10 @@ Error: ${e}`,
for (const faction of [...Player.factions, ...Player.factionInvitations]) {
Player.factionRumors.add(faction);
}
for (const person of [Player, ...Player.sleeves]) {
person.persistentIntelligenceData.exp = person.exp.intelligence;
person.overrideIntelligence();
}
showAPIBreaks("3.0.0", breakingChanges300);
}
}

View File

@@ -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`,
);
});
});

Binary file not shown.

View File

@@ -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", () => {

View File

@@ -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": {