mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2026-04-17 23:08:36 +02:00
MISC: Rework intelligence override (#2575)
This commit is contained in:
@@ -289,26 +289,35 @@ function IntelligenceOverride({
|
|||||||
tooltip={
|
tooltip={
|
||||||
<>
|
<>
|
||||||
<Typography component="div">
|
<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>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
If your Intelligence is 1000 and you set this value to 500, the "effective" Intelligence, which is used
|
If your intelligence is 1000 and you set this value to 500, your intelligence will be temporarily set to
|
||||||
for bonus calculation, is only 500.
|
500.
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
If a Sleeve's Intelligence is 200 and you set this value to 500, the "effective" Intelligence, which is
|
If a Sleeve's intelligence is 200 and you set this value to 500, that Sleeve's intelligence is still
|
||||||
used for bonus calculation, is still 200.
|
200.
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography>
|
<Typography>
|
||||||
You will still gain Intelligence experience as normal. Intelligence Override only affects the Intelligence
|
Note that you still gain intelligence experience as normal.
|
||||||
bonus.
|
<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>
|
</Typography>
|
||||||
<br />
|
<br />
|
||||||
<Typography>
|
<Typography>
|
||||||
The "effective" Intelligence will be shown in the character overview. If the effective value is different
|
The overridden intelligence will be shown in the character overview. You can hover your mouse over it to see
|
||||||
from the original value, you can hover your mouse over it to see the original value.
|
the original value.
|
||||||
</Typography>
|
</Typography>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ export const CONSTANTS = {
|
|||||||
VersionString: "3.0.0dev",
|
VersionString: "3.0.0dev",
|
||||||
isDevBranch: true,
|
isDevBranch: true,
|
||||||
isInTestEnvironment: globalThis.process?.env?.JEST_WORKER_ID !== undefined,
|
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
|
/** 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
|
* and the skill level formula in Player.js. Note that all this means it that when experience hits MAX_INT, then
|
||||||
|
|||||||
@@ -76,6 +76,7 @@ function setStatLevel(stat: string, level: number): void {
|
|||||||
break;
|
break;
|
||||||
case "Intelligence":
|
case "Intelligence":
|
||||||
Player.exp.intelligence = 0;
|
Player.exp.intelligence = 0;
|
||||||
|
Player.persistentIntelligenceData.exp = 0;
|
||||||
Player.gainIntelligenceExp(calculateExp(level, 1));
|
Player.gainIntelligenceExp(calculateExp(level, 1));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -107,6 +108,7 @@ function resetAllExp(): void {
|
|||||||
Player.exp.agility = 0;
|
Player.exp.agility = 0;
|
||||||
Player.exp.charisma = 0;
|
Player.exp.charisma = 0;
|
||||||
Player.exp.intelligence = 0;
|
Player.exp.intelligence = 0;
|
||||||
|
Player.persistentIntelligenceData.exp = 0;
|
||||||
Player.updateSkillLevels();
|
Player.updateSkillLevels();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,6 +135,7 @@ function resetExperience(stat: string): () => void {
|
|||||||
break;
|
break;
|
||||||
case "Intelligence":
|
case "Intelligence":
|
||||||
Player.exp.intelligence = 0;
|
Player.exp.intelligence = 0;
|
||||||
|
Player.persistentIntelligenceData.exp = 0;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
Player.updateSkillLevels();
|
Player.updateSkillLevels();
|
||||||
@@ -155,6 +158,7 @@ function enableIntelligence(): void {
|
|||||||
function disableIntelligence(): void {
|
function disableIntelligence(): void {
|
||||||
Player.exp.intelligence = 0;
|
Player.exp.intelligence = 0;
|
||||||
Player.skills.intelligence = 0;
|
Player.skills.intelligence = 0;
|
||||||
|
Player.persistentIntelligenceData.exp = 0;
|
||||||
Player.updateSkillLevels();
|
Player.updateSkillLevels();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { currentNodeMults } from "../BitNode/BitNodeMultipliers";
|
|||||||
import { CONSTANTS } from "../Constants";
|
import { CONSTANTS } from "../Constants";
|
||||||
import { Player } from "../Player";
|
import { Player } from "../Player";
|
||||||
import { defaultMultipliers } from "./Multipliers";
|
import { defaultMultipliers } from "./Multipliers";
|
||||||
import { calculateSkill } from "./formulas/skill";
|
import { calculateExp, calculateSkill } from "./formulas/skill";
|
||||||
|
|
||||||
// Base class representing a person-like object
|
// Base class representing a person-like object
|
||||||
export abstract class Person implements IPerson {
|
export abstract class Person implements IPerson {
|
||||||
@@ -34,6 +34,10 @@ export abstract class Person implements IPerson {
|
|||||||
intelligence: 0,
|
intelligence: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
persistentIntelligenceData = {
|
||||||
|
exp: 0,
|
||||||
|
};
|
||||||
|
|
||||||
mults = defaultMultipliers();
|
mults = defaultMultipliers();
|
||||||
|
|
||||||
/** Augmentations */
|
/** 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 {
|
gainIntelligenceExp(exp: number): void {
|
||||||
if (isNaN(exp)) {
|
if (isNaN(exp)) {
|
||||||
console.error("ERROR: NaN passed into Player.gainIntelligenceExp()");
|
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) {
|
if (Player.sourceFileLvl(5) > 0 || this.skills.intelligence > 0 || Player.bitNodeN === 5) {
|
||||||
this.exp.intelligence += exp;
|
this.exp.intelligence += exp;
|
||||||
this.skills.intelligence = Math.floor(this.calculateSkill(this.exp.intelligence, 1));
|
this.skills.intelligence = Math.floor(this.calculateSkill(this.exp.intelligence, 1));
|
||||||
|
this.persistentIntelligenceData.exp += exp;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -140,6 +140,7 @@ export function prestigeAugmentation(this: PlayerObject): void {
|
|||||||
export function prestigeSourceFile(this: PlayerObject): void {
|
export function prestigeSourceFile(this: PlayerObject): void {
|
||||||
this.entropy = 0;
|
this.entropy = 0;
|
||||||
this.prestigeAugmentation();
|
this.prestigeAugmentation();
|
||||||
|
this.overrideIntelligence();
|
||||||
this.karma = 0;
|
this.karma = 0;
|
||||||
// Duplicate sleeves are reset to level 1 every Bit Node (but the number of sleeves you have persists)
|
// Duplicate sleeves are reset to level 1 every Bit Node (but the number of sleeves you have persists)
|
||||||
this.sleeves.forEach((sleeve) => sleeve.prestige());
|
this.sleeves.forEach((sleeve) => sleeve.prestige());
|
||||||
|
|||||||
@@ -251,6 +251,8 @@ export class Sleeve extends Person implements SleevePerson {
|
|||||||
this.shock = 100;
|
this.shock = 100;
|
||||||
this.storedCycles = 0;
|
this.storedCycles = 0;
|
||||||
this.sync = Math.max(this.memory, 1);
|
this.sync = Math.max(this.memory, 1);
|
||||||
|
|
||||||
|
this.overrideIntelligence();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,9 +1,3 @@
|
|||||||
import { Player } from "@player";
|
|
||||||
|
|
||||||
export function calculateIntelligenceBonus(intelligence: number, weight = 1): number {
|
export function calculateIntelligenceBonus(intelligence: number, weight = 1): number {
|
||||||
const effectiveIntelligence =
|
return 1 + (weight * Math.pow(intelligence, 0.8)) / 600;
|
||||||
Player.bitNodeOptions.intelligenceOverride !== undefined
|
|
||||||
? Math.min(Player.bitNodeOptions.intelligenceOverride, intelligence)
|
|
||||||
: intelligence;
|
|
||||||
return 1 + (weight * Math.pow(effectiveIntelligence, 0.8)) / 600;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ export function enterBitNode(
|
|||||||
} else if (Player.sourceFileLvl(5) === 0 && newBitNode !== 5) {
|
} else if (Player.sourceFileLvl(5) === 0 && newBitNode !== 5) {
|
||||||
Player.skills.intelligence = 0;
|
Player.skills.intelligence = 0;
|
||||||
Player.exp.intelligence = 0;
|
Player.exp.intelligence = 0;
|
||||||
|
Player.persistentIntelligenceData.exp = 0;
|
||||||
}
|
}
|
||||||
if (newBitNode === 5 && Player.skills.intelligence === 0) {
|
if (newBitNode === 5 && Player.skills.intelligence === 0) {
|
||||||
Player.skills.intelligence = 1;
|
Player.skills.intelligence = 1;
|
||||||
|
|||||||
@@ -71,15 +71,15 @@ export function Val({ name, color }: ValProps): React.ReactElement {
|
|||||||
return clearSubscription;
|
return clearSubscription;
|
||||||
}, [name]);
|
}, [name]);
|
||||||
|
|
||||||
if (
|
if (name === "Int" && Player.bitNodeOptions.intelligenceOverride !== undefined) {
|
||||||
name === "Int" &&
|
|
||||||
Player.bitNodeOptions.intelligenceOverride !== undefined &&
|
|
||||||
Player.bitNodeOptions.intelligenceOverride < Player.skills.intelligence
|
|
||||||
) {
|
|
||||||
return (
|
return (
|
||||||
<Tooltip title={`Intelligence: ${formatSkill(Player.skills.intelligence)}`}>
|
<Tooltip
|
||||||
|
title={`Persistent Intelligence: ${formatSkill(
|
||||||
|
Player.calculateSkill(Player.persistentIntelligenceData.exp, 1),
|
||||||
|
)}`}
|
||||||
|
>
|
||||||
<Typography color={color}>
|
<Typography color={color}>
|
||||||
{formatSkill(Player.bitNodeOptions.intelligenceOverride)}
|
{formatSkill(Player.skills.intelligence)}
|
||||||
<sup>*</sup>
|
<sup>*</sup>
|
||||||
</Typography>
|
</Typography>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@@ -140,7 +140,7 @@ export function CharacterOverview({ parentOpen, save, killScripts }: OverviewPro
|
|||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Table sx={{ display: "block", m: 1 }}>
|
<Table sx={{ display: "block", p: 1 }}>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
<DataRow name="HP" showBar={false} color={theme.colors.hp} cellType={"cellNone"} />
|
<DataRow name="HP" showBar={false} color={theme.colors.hp} cellType={"cellNone"} />
|
||||||
<DataRow name="Money" showBar={false} color={theme.colors.money} cellType={"cell"} />
|
<DataRow name="Money" showBar={false} color={theme.colors.money} cellType={"cell"} />
|
||||||
|
|||||||
@@ -625,6 +625,10 @@ Error: ${e}`,
|
|||||||
for (const faction of [...Player.factions, ...Player.factionInvitations]) {
|
for (const faction of [...Player.factions, ...Player.factionInvitations]) {
|
||||||
Player.factionRumors.add(faction);
|
Player.factionRumors.add(faction);
|
||||||
}
|
}
|
||||||
|
for (const person of [Player, ...Player.sleeves]) {
|
||||||
|
person.persistentIntelligenceData.exp = person.exp.intelligence;
|
||||||
|
person.overrideIntelligence();
|
||||||
|
}
|
||||||
showAPIBreaks("3.0.0", breakingChanges300);
|
showAPIBreaks("3.0.0", breakingChanges300);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,46 +1,34 @@
|
|||||||
import { Player } from "@player";
|
import { Player } from "@player";
|
||||||
import fs from "node:fs";
|
import fs from "node:fs";
|
||||||
import type { ScriptFilePath } from "../../../src/Paths/ScriptFilePath";
|
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 db from "../../../src/db";
|
||||||
import * as FileUtils from "../../../src/utils/FileUtils";
|
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", () => {
|
describe("v3", () => {
|
||||||
test("v2.8.1 to v3.0.0", async () => {
|
test("v2.8.1 to v3.0.0", async () => {
|
||||||
const saveData = new Uint8Array(fs.readFileSync("test/jest/Migration/save-files/v2.8.1.gz"));
|
const saveData = new Uint8Array(fs.readFileSync("test/jest/Migration/save-files/v2.8.1.gz"));
|
||||||
|
const mockedDownload = await loadGameFromSaveData(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)));
|
|
||||||
|
|
||||||
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();
|
|
||||||
|
|
||||||
// Check if auto-migration works
|
// Check if auto-migration works
|
||||||
expect(
|
expect(
|
||||||
@@ -57,4 +45,64 @@ describe("v3", () => {
|
|||||||
// Check if evaluateVersionCompatibility correctly loads the data in IndexedDB and passes it to downloadContentAsFile
|
// 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");
|
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 { getTorRouter } from "../../../src/Server/ServerHelpers";
|
||||||
import * as exceptionAlertModule from "../../../src/utils/helpers/exceptionAlert";
|
import * as exceptionAlertModule from "../../../src/utils/helpers/exceptionAlert";
|
||||||
|
|
||||||
const nextBN = 3;
|
const nextBN = 4;
|
||||||
|
|
||||||
function setNumBlackOpsComplete(value: number): void {
|
function setNumBlackOpsComplete(value: number): void {
|
||||||
if (!Player.bladeburner) {
|
if (!Player.bladeburner) {
|
||||||
@@ -48,19 +48,139 @@ beforeAll(() => {
|
|||||||
initGameEnvironment();
|
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", () => {
|
describe("b1tflum3", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
setupBasicTestingEnvironment();
|
setupBasicTestingEnvironment();
|
||||||
Player.queueAugmentation(AugmentationName.TheRedPill);
|
Player.queueAugmentation(AugmentationName.Targeting1);
|
||||||
installAugmentations();
|
installAugmentations();
|
||||||
Player.gainHackingExp(1e100);
|
Player.gainHackingExp(1e100);
|
||||||
const wdServer = GetServerOrThrow(SpecialServers.WorldDaemon);
|
|
||||||
wdServer.hasAdminRights = true;
|
|
||||||
});
|
});
|
||||||
// Make sure that the player is in the next BN without SF rewards.
|
// Make sure that the player is in the next BN without SF rewards.
|
||||||
const expectSucceedInB1tflum3 = () => {
|
const expectSucceedInB1tflum3 = () => {
|
||||||
expect(Player.bitNodeN).toStrictEqual(nextBN);
|
expect(Player.bitNodeN).toStrictEqual(nextBN);
|
||||||
expect(Player.augmentations.length).toStrictEqual(0);
|
expect(Player.augmentations.length).toStrictEqual(0);
|
||||||
|
expect(Player.exp.hacking).toStrictEqual(0);
|
||||||
expect(Player.sourceFileLvl(1)).toStrictEqual(0);
|
expect(Player.sourceFileLvl(1)).toStrictEqual(0);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -79,15 +199,20 @@ describe("b1tflum3", () => {
|
|||||||
});
|
});
|
||||||
expectSucceedInB1tflum3();
|
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", () => {
|
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", () => {
|
test("Invalid intelligenceOverride", () => {
|
||||||
const ns = getNS();
|
const ns = getNS();
|
||||||
expect(() => {
|
expect(() => {
|
||||||
@@ -114,13 +239,7 @@ describe("b1tflum3", () => {
|
|||||||
describe("destroyW0r1dD43m0n", () => {
|
describe("destroyW0r1dD43m0n", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
setupBasicTestingEnvironment();
|
setupBasicTestingEnvironment();
|
||||||
Player.queueAugmentation(AugmentationName.TheRedPill);
|
setUpBeforeDestroyingWD();
|
||||||
installAugmentations();
|
|
||||||
Player.gainHackingExp(1e100);
|
|
||||||
const wdServer = GetServerOrThrow(SpecialServers.WorldDaemon);
|
|
||||||
wdServer.hasAdminRights = true;
|
|
||||||
Player.startBladeburner();
|
|
||||||
setNumBlackOpsComplete(blackOpsArray.length);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("Success", () => {
|
describe("Success", () => {
|
||||||
@@ -162,6 +281,9 @@ describe("destroyW0r1dD43m0n", () => {
|
|||||||
});
|
});
|
||||||
expectSucceedInDestroyingWD();
|
expectSucceedInDestroyingWD();
|
||||||
});
|
});
|
||||||
|
test("intelligenceOverride", () => {
|
||||||
|
testIntelligenceOverride(getNS(), "destroyW0r1dD43m0n", expectSucceedInDestroyingWD, setUpBeforeDestroyingWD);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("Failure", () => {
|
describe("Failure", () => {
|
||||||
@@ -488,6 +610,7 @@ describe("purchaseProgram", () => {
|
|||||||
const spiedExceptionAlert = jest.spyOn(exceptionAlertModule, "exceptionAlert");
|
const spiedExceptionAlert = jest.spyOn(exceptionAlertModule, "exceptionAlert");
|
||||||
const ns = getNS();
|
const ns = getNS();
|
||||||
expect(Player.hasProgram(CompletedProgramName.darkscape)).toStrictEqual(false);
|
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(ns.singularity.purchaseProgram(CompletedProgramName.darkscape.toLowerCase())).toStrictEqual(true);
|
||||||
expect(Player.hasProgram(CompletedProgramName.darkscape)).toStrictEqual(true);
|
expect(Player.hasProgram(CompletedProgramName.darkscape)).toStrictEqual(true);
|
||||||
expect(spiedExceptionAlert).not.toHaveBeenCalled();
|
expect(spiedExceptionAlert).not.toHaveBeenCalled();
|
||||||
@@ -507,6 +630,7 @@ describe("purchaseProgram", () => {
|
|||||||
});
|
});
|
||||||
test("Invalid program name", () => {
|
test("Invalid program name", () => {
|
||||||
const ns = getNS();
|
const ns = getNS();
|
||||||
|
// @ts-expect-error - Intentionally use invalid program name
|
||||||
expect(ns.singularity.purchaseProgram("InvalidProgram.exe")).toStrictEqual(false);
|
expect(ns.singularity.purchaseProgram("InvalidProgram.exe")).toStrictEqual(false);
|
||||||
});
|
});
|
||||||
test("Not enough money", () => {
|
test("Not enough money", () => {
|
||||||
|
|||||||
@@ -534,6 +534,9 @@ exports[`Check Save File Continuity PlayerSave continuity 1`] = `
|
|||||||
"work_money": 1,
|
"work_money": 1,
|
||||||
},
|
},
|
||||||
"numPeopleKilled": 0,
|
"numPeopleKilled": 0,
|
||||||
|
"persistentIntelligenceData": {
|
||||||
|
"exp": 0,
|
||||||
|
},
|
||||||
"playtimeSinceLastAug": 0,
|
"playtimeSinceLastAug": 0,
|
||||||
"playtimeSinceLastBitnode": 0,
|
"playtimeSinceLastBitnode": 0,
|
||||||
"purchasedServers": [],
|
"purchasedServers": [],
|
||||||
@@ -614,6 +617,9 @@ exports[`Check Save File Continuity PlayerSave continuity 1`] = `
|
|||||||
"strength_exp": 1,
|
"strength_exp": 1,
|
||||||
"work_money": 1,
|
"work_money": 1,
|
||||||
},
|
},
|
||||||
|
"persistentIntelligenceData": {
|
||||||
|
"exp": 0,
|
||||||
|
},
|
||||||
"queuedAugmentations": [],
|
"queuedAugmentations": [],
|
||||||
"shock": 100,
|
"shock": 100,
|
||||||
"skills": {
|
"skills": {
|
||||||
@@ -686,6 +692,9 @@ exports[`Check Save File Continuity PlayerSave continuity 1`] = `
|
|||||||
"strength_exp": 1,
|
"strength_exp": 1,
|
||||||
"work_money": 1,
|
"work_money": 1,
|
||||||
},
|
},
|
||||||
|
"persistentIntelligenceData": {
|
||||||
|
"exp": 0,
|
||||||
|
},
|
||||||
"queuedAugmentations": [],
|
"queuedAugmentations": [],
|
||||||
"shock": 100,
|
"shock": 100,
|
||||||
"skills": {
|
"skills": {
|
||||||
|
|||||||
Reference in New Issue
Block a user