Compare commits

...

8 Commits

27 changed files with 1304 additions and 852 deletions
+1 -1
View File
@@ -12,7 +12,7 @@ Default value:
- All boolean options: false - All boolean options: false
If you specify intelligenceOverride, it must be a non-negative integer. If you specify intelligenceOverride, it must be a positive integer.
**Signature:** **Signature:**
+1 -1
View File
@@ -123,7 +123,7 @@ Default value:
- All boolean options: false - All boolean options: false
If you specify intelligenceOverride, it must be a non-negative integer. If you specify intelligenceOverride, it must be a positive integer.
</td></tr> </td></tr>
+1 -10
View File
@@ -4,19 +4,10 @@ import { AchievementList } from "./AchievementList";
import { achievements } from "./Achievements"; import { achievements } from "./Achievements";
import { Box, Typography } from "@mui/material"; import { Box, Typography } from "@mui/material";
import { Player } from "@player"; import { Player } from "@player";
import { makeStyles } from "tss-react/mui";
const useStyles = makeStyles()({
root: {
width: 50,
userSelect: "none",
},
});
export function AchievementsRoot(): JSX.Element { export function AchievementsRoot(): JSX.Element {
const { classes } = useStyles();
return ( return (
<div className={classes.root} style={{ width: "100%" }}> <div style={{ width: "100%" }}>
<Typography variant="h4">Achievements</Typography> <Typography variant="h4">Achievements</Typography>
<Box mx={2}> <Box mx={2}>
<Typography> <Typography>
+3 -3
View File
@@ -271,13 +271,13 @@ function IntelligenceOverride({
disabled={!enabled} disabled={!enabled}
value={intelligenceOverride !== undefined ? intelligenceOverride : ""} value={intelligenceOverride !== undefined ? intelligenceOverride : ""}
onChange={(event) => { onChange={(event) => {
// Empty string will be automatically changed to "0". // Empty string will be automatically changed to "1".
if (event.target.value === "") { if (event.target.value === "") {
callbacks.setIntelligenceOverride(0); callbacks.setIntelligenceOverride(1);
return; return;
} }
const value = Number.parseInt(event.target.value); const value = Number.parseInt(event.target.value);
if (!Number.isInteger(value) || value < 0) { if (!Number.isInteger(value) || value < 1) {
return; return;
} }
callbacks.setIntelligenceOverride(value); callbacks.setIntelligenceOverride(value);
+25 -4
View File
@@ -6,6 +6,8 @@ import { ActionClass, ActionParams } from "./Action";
import { operationSkillSuccessBonus, operationTeamSuccessBonus } from "./Operation"; import { operationSkillSuccessBonus, operationTeamSuccessBonus } from "./Operation";
import { getEnumHelper } from "../../utils/EnumHelper"; import { getEnumHelper } from "../../utils/EnumHelper";
import type { TeamActionWithCasualties } from "./TeamCasualties"; import type { TeamActionWithCasualties } from "./TeamCasualties";
import { constructorsForReviver, Generic_fromJSON, type IReviverValue } from "../../utils/JSONReviver";
import { clampInteger } from "../../utils/helpers/clampNumber";
interface BlackOpParams { interface BlackOpParams {
name: BladeburnerBlackOpName; name: BladeburnerBlackOpName;
@@ -32,11 +34,11 @@ export class BlackOperation extends ActionClass implements TeamActionWithCasualt
return getEnumHelper("BladeburnerBlackOpName").isMember(name); return getEnumHelper("BladeburnerBlackOpName").isMember(name);
} }
constructor(params: ActionParams & BlackOpParams) { constructor(params: (ActionParams & BlackOpParams) | null = null) {
super(params); super(params);
this.name = params.name; this.name = params?.name ?? BladeburnerBlackOpName.OperationTyphoon;
this.reqdRank = params.reqdRank; this.reqdRank = params?.reqdRank ?? 0;
this.n = params.n; this.n = params?.n ?? 0;
} }
getAvailability(bladeburner: Bladeburner): Availability { getAvailability(bladeburner: Bladeburner): Availability {
@@ -65,4 +67,23 @@ export class BlackOperation extends ActionClass implements TeamActionWithCasualt
getTeamSuccessBonus = operationTeamSuccessBonus; getTeamSuccessBonus = operationTeamSuccessBonus;
getActionTypeSkillSuccessBonus = operationSkillSuccessBonus; getActionTypeSkillSuccessBonus = operationSkillSuccessBonus;
toJSON(): IReviverValue {
return {
ctor: "BlackOperation",
data: {
teamCount: this.teamCount,
},
};
} }
loadData(loadedObject: BlackOperation): void {
this.teamCount = clampInteger(loadedObject.teamCount, 0);
}
static fromJSON(value: IReviverValue): BlackOperation {
return Generic_fromJSON(BlackOperation, value.data);
}
}
constructorsForReviver.BlackOperation = BlackOperation;
+10 -4
View File
@@ -44,12 +44,18 @@ export function resolveTeamCasualties(action: TeamActionWithCasualties, team: Op
*/ */
const losses = const losses =
minCasualties <= maxCasualties ? team.getTeamCasualtiesRoll(minCasualties, maxCasualties) : minCasualties; minCasualties <= maxCasualties ? team.getTeamCasualtiesRoll(minCasualties, maxCasualties) : minCasualties;
team.teamSize -= losses; // Calculate the new teamSize in a temporary variable and call the setter team.teamSize ONCE.
if (team.teamSize < team.sleeveSize) { // Note that it's important to call the setter only once; otherwise, the team count of each operation won't be reset
team.killRandomSupportingSleeves(team.sleeveSize - team.teamSize); // correctly.
// For example, if _teamSize is 9 (1 team member + 8 support sleeves) and "losses" is 9, calling the setter with
// (team.teamSize - losses) will set teamCount of ops/blackOps to 0 while it should be 8.
let newTeamSize = team.teamSize - losses;
if (newTeamSize < team.sleeveSize) {
team.killRandomSupportingSleeves(team.sleeveSize - newTeamSize);
// If this happens, all team members died and some sleeves took damage. In this case, teamSize = sleeveSize. // If this happens, all team members died and some sleeves took damage. In this case, teamSize = sleeveSize.
team.teamSize = team.sleeveSize; newTeamSize = team.sleeveSize;
} }
team.teamSize = newTeamSize;
team.teamLost += losses; team.teamLost += losses;
return losses; return losses;
+50 -9
View File
@@ -47,7 +47,7 @@ import { createContracts, loadContractsData } from "./data/Contracts";
import { createOperations, loadOperationsData } from "./data/Operations"; import { createOperations, loadOperationsData } from "./data/Operations";
import { clampInteger, clampNumber } from "../utils/helpers/clampNumber"; import { clampInteger, clampNumber } from "../utils/helpers/clampNumber";
import { parseCommand } from "../Terminal/Parser"; import { parseCommand } from "../Terminal/Parser";
import { BlackOperations } from "./data/BlackOperations"; import { createBlackOperations, loadBlackOperationsData } from "./data/BlackOperations";
import { GeneralActions } from "./data/GeneralActions"; import { GeneralActions } from "./data/GeneralActions";
import { PlayerObject } from "../PersonObjects/Player/PlayerObject"; import { PlayerObject } from "../PersonObjects/Player/PlayerObject";
import { Sleeve } from "../PersonObjects/Sleeve/Sleeve"; import { Sleeve } from "../PersonObjects/Sleeve/Sleeve";
@@ -72,7 +72,31 @@ export class Bladeburner implements OperationTeam {
skillPoints = 0; skillPoints = 0;
totalSkillPoints = 0; totalSkillPoints = 0;
teamSize = 0; /**
* Do NOT directly read and write this field. You must use the getter/setter.
* We use _teamSize instead of a private field #teamSize to reduce the complexity of saving/loading code.
*/
_teamSize = 0;
get teamSize() {
return this._teamSize;
}
set teamSize(value: number) {
// Ensure teamSize is a non-negative integer.
let newSize = value;
if (!Number.isInteger(newSize) || newSize < 0) {
newSize = 0;
}
// Early return if there is no change.
if (this._teamSize === newSize) {
return;
}
this._teamSize = newSize;
// Reduce teamCount of actions if it's greater than the team size.
for (const action of [...Object.values(this.operations), ...Object.values(this.blackOperations)]) {
action.teamCount = Math.min(action.teamCount, this._teamSize);
}
}
get sleeveSize() { get sleeveSize() {
return Player.sleevesSupportingBladeburner().length; return Player.sleevesSupportingBladeburner().length;
} }
@@ -96,9 +120,13 @@ export class Bladeburner implements OperationTeam {
staminaBonus = 0; staminaBonus = 0;
maxStamina = 1; maxStamina = 1;
stamina = 1; stamina = 1;
// Contracts and operations are stored on the Bladeburner object even though they are global so that they can utilize save/load of the main bladeburner object // Contracts, operations and blackOps are stored on the Bladeburner object even though they are global so that they
// can utilize save/load of the main bladeburner object
contracts: Record<BladeburnerContractName, Contract>; contracts: Record<BladeburnerContractName, Contract>;
operations: Record<BladeburnerOperationName, Operation>; operations: Record<BladeburnerOperationName, Operation>;
blackOperations: Record<BladeburnerBlackOpName, BlackOperation>;
// Array for quick lookup by BlackOp number
blackOperationArray: BlackOperation[];
numBlackOpsComplete = 0; numBlackOpsComplete = 0;
logging = { logging = {
general: true, general: true,
@@ -119,6 +147,11 @@ export class Bladeburner implements OperationTeam {
constructor() { constructor() {
this.contracts = createContracts(); this.contracts = createContracts();
this.operations = createOperations(); this.operations = createOperations();
this.blackOperations = createBlackOperations();
this.blackOperationArray = Object.values(this.blackOperations).sort((a, b) => (a.n < b.n ? -1 : 1));
if (!this.blackOperationArray.every((blackOp, i) => blackOp.n === i)) {
throw new Error("blackOperationArray is not initialized with correct indices");
}
} }
// Initialization code that is dependent on Player is here instead of in the constructor // Initialization code that is dependent on Player is here instead of in the constructor
@@ -1407,7 +1440,7 @@ export class Bladeburner implements OperationTeam {
case BladeburnerActionType.Operation: case BladeburnerActionType.Operation:
return this.operations[actionId.name]; return this.operations[actionId.name];
case BladeburnerActionType.BlackOp: case BladeburnerActionType.BlackOp:
return BlackOperations[actionId.name]; return this.blackOperations[actionId.name];
case BladeburnerActionType.General: case BladeburnerActionType.General:
return GeneralActions[actionId.name]; return GeneralActions[actionId.name];
} }
@@ -1426,7 +1459,7 @@ export class Bladeburner implements OperationTeam {
case BladeburnerActionType.Operation: case BladeburnerActionType.Operation:
return this.operations[name as BladeburnerOperationName]; return this.operations[name as BladeburnerOperationName];
case BladeburnerActionType.BlackOp: case BladeburnerActionType.BlackOp:
return BlackOperations[name as BladeburnerBlackOpName]; return this.blackOperations[name as BladeburnerBlackOpName];
} }
} }
@@ -1437,9 +1470,11 @@ export class Bladeburner implements OperationTeam {
return id ? this.getActionObject(id) : null; return id ? this.getActionObject(id) : null;
} }
static keysToSave = getKeyList(Bladeburner, { removedKeys: ["skillMultipliers"] }); static keysToSave = getKeyList(Bladeburner, { removedKeys: ["skillMultipliers", "blackOperationArray"] });
// Don't load contracts or operations because of the special loading method they use, see fromJSON // Don't load contracts or operations because of the special loading method they use, see fromJSON
static keysToLoad = getKeyList(Bladeburner, { removedKeys: ["skillMultipliers", "contracts", "operations"] }); static keysToLoad = getKeyList(Bladeburner, {
removedKeys: ["skillMultipliers", "contracts", "operations", "blackOperations", "blackOperationArray"],
});
/** Serialize the current object to a JSON save state. */ /** Serialize the current object to a JSON save state. */
toJSON(): IReviverValue { toJSON(): IReviverValue {
@@ -1449,9 +1484,10 @@ export class Bladeburner implements OperationTeam {
/** Initializes a Bladeburner object from a JSON save state. */ /** Initializes a Bladeburner object from a JSON save state. */
static fromJSON(value: IReviverValue): Bladeburner { static fromJSON(value: IReviverValue): Bladeburner {
assertObject(value.data); assertObject(value.data);
// operations and contracts are not loaded directly from the save, we load them in using a different method // Contracts, operations, and black ops are not loaded directly from the save; they are loaded via a different method.
const contractsData = value.data.contracts; const contractsData = value.data.contracts;
const operationsData = value.data.operations; const operationsData = value.data.operations;
const blackOperationsData = value.data.blackOperations;
const bladeburner = Generic_fromJSON(Bladeburner, value.data, Bladeburner.keysToLoad); const bladeburner = Generic_fromJSON(Bladeburner, value.data, Bladeburner.keysToLoad);
/** /**
@@ -1472,10 +1508,11 @@ export class Bladeburner implements OperationTeam {
bladeburner.automateActionLow = loadActionIdentifier(bladeburner.automateActionLow); bladeburner.automateActionLow = loadActionIdentifier(bladeburner.automateActionLow);
} }
} }
// Loading this way allows better typesafety and also allows faithfully reconstructing contracts/operations // Loading this way allows better typesafety and also allows faithfully reconstructing contracts/operations/blackOps
// even from save data that is missing a lot of static info about the objects. // even from save data that is missing a lot of static info about the objects.
loadContractsData(contractsData, bladeburner.contracts); loadContractsData(contractsData, bladeburner.contracts);
loadOperationsData(operationsData, bladeburner.operations); loadOperationsData(operationsData, bladeburner.operations);
loadBlackOperationsData(blackOperationsData, bladeburner.blackOperations);
// Regenerate skill multiplier data, which is not included in savedata // Regenerate skill multiplier data, which is not included in savedata
bladeburner.updateSkillMultipliers(); bladeburner.updateSkillMultipliers();
// If stamina or maxStamina is invalid, we set both of them to 1 and recalculate them. // If stamina or maxStamina is invalid, we set both of them to 1 and recalculate them.
@@ -1488,6 +1525,10 @@ export class Bladeburner implements OperationTeam {
bladeburner.maxStamina = 1; bladeburner.maxStamina = 1;
bladeburner.calculateMaxStamina(); bladeburner.calculateMaxStamina();
} }
// "_teamSize" was "teamSize" in pre-v3 versions.
if ("teamSize" in value.data && Number.isFinite(value.data.teamSize)) {
bladeburner.teamSize = value.data.teamSize as number;
}
return bladeburner; return bladeburner;
} }
} }
+22 -7
View File
@@ -1,7 +1,9 @@
import { assertLoadingType } from "../../utils/TypeAssertion";
import { BlackOperation } from "../Actions/BlackOperation"; import { BlackOperation } from "../Actions/BlackOperation";
import { BladeburnerBlackOpName, CityName, FactionName } from "@enums"; import { BladeburnerBlackOpName, CityName, FactionName } from "@enums";
export const BlackOperations: Record<BladeburnerBlackOpName, BlackOperation> = { export function createBlackOperations(): Record<BladeburnerBlackOpName, BlackOperation> {
return {
[BladeburnerBlackOpName.OperationTyphoon]: new BlackOperation({ [BladeburnerBlackOpName.OperationTyphoon]: new BlackOperation({
name: BladeburnerBlackOpName.OperationTyphoon, name: BladeburnerBlackOpName.OperationTyphoon,
n: 0, n: 0,
@@ -728,10 +730,23 @@ export const BlackOperations: Record<BladeburnerBlackOpName, BlackOperation> = {
desc: "Yesterday we obeyed kings and bent our necks to emperors. Today we kneel only to truth.", desc: "Yesterday we obeyed kings and bent our necks to emperors. Today we kneel only to truth.",
}), }),
}; };
}
/** Array for quick lookup by blackop number */
export const blackOpsArray = Object.values(BlackOperations).sort((a, b) => (a.n < b.n ? -1 : 1)); export const numberOfBlackOperations = Object.keys(BladeburnerBlackOpName).length;
// Verify that all "n" properties match the index in the array
if (!blackOpsArray.every((blackOp, i) => blackOp.n === i)) { export function loadBlackOperationsData(
throw new Error("blackOpsArray did not initialize with correct indices"); data: unknown,
blackOperations: Record<BladeburnerBlackOpName, BlackOperation>,
) {
if (data == null || typeof data !== "object" || Array.isArray(data)) {
return;
}
assertLoadingType<Record<BladeburnerBlackOpName, unknown>>(data);
for (const blackOpName of Object.values(BladeburnerBlackOpName)) {
const loadedBlackOp = data[blackOpName];
if (!(loadedBlackOp instanceof BlackOperation)) {
continue;
}
blackOperations[blackOpName].loadData(loadedBlackOp);
}
} }
+3 -1
View File
@@ -113,7 +113,9 @@ export function createContracts(): Record<BladeburnerContractName, Contract> {
export function loadContractsData(data: unknown, contracts: Record<BladeburnerContractName, Contract>) { export function loadContractsData(data: unknown, contracts: Record<BladeburnerContractName, Contract>) {
// loading data as "unknown" and typechecking it down is probably not necessary // loading data as "unknown" and typechecking it down is probably not necessary
// but this will prevent crashes even with malformed savedata // but this will prevent crashes even with malformed savedata
if (!data || typeof data !== "object") return; if (data == null || typeof data !== "object" || Array.isArray(data)) {
return;
}
assertLoadingType<Record<BladeburnerContractName, unknown>>(data); assertLoadingType<Record<BladeburnerContractName, unknown>>(data);
for (const contractName of Object.values(BladeburnerContractName)) { for (const contractName of Object.values(BladeburnerContractName)) {
const loadedContract = data[contractName]; const loadedContract = data[contractName];
+3 -1
View File
@@ -230,7 +230,9 @@ export function createOperations(): Record<BladeburnerOperationName, Operation>
export function loadOperationsData(data: unknown, operations: Record<BladeburnerOperationName, Operation>) { export function loadOperationsData(data: unknown, operations: Record<BladeburnerOperationName, Operation>) {
// loading data as "unknown" and typechecking it down is probably not necessary // loading data as "unknown" and typechecking it down is probably not necessary
// but this will prevent crashes even with malformed savedata // but this will prevent crashes even with malformed savedata
if (!data || typeof data !== "object") return; if (data == null || typeof data !== "object" || Array.isArray(data)) {
return;
}
assertLoadingType<Record<BladeburnerOperationName, unknown>>(data); assertLoadingType<Record<BladeburnerOperationName, unknown>>(data);
for (const operationName of Object.values(BladeburnerOperationName)) { for (const operationName of Object.values(BladeburnerOperationName)) {
const loadedOperation = data[operationName]; const loadedOperation = data[operationName];
+4 -4
View File
@@ -7,7 +7,7 @@ import { BlackOpElem } from "./BlackOpElem";
import { Router } from "../../ui/GameRoot"; import { Router } from "../../ui/GameRoot";
import { Page } from "../../ui/Router"; import { Page } from "../../ui/Router";
import { CorruptibleText } from "../../ui/React/CorruptibleText"; import { CorruptibleText } from "../../ui/React/CorruptibleText";
import { blackOpsArray } from "../data/BlackOperations"; import { numberOfBlackOperations } from "../data/BlackOperations";
import { finishBitNode } from "../../BitNode/BitNodeUtils"; import { finishBitNode } from "../../BitNode/BitNodeUtils";
import { Player } from "@player"; import { Player } from "@player";
@@ -16,7 +16,7 @@ interface BlackOpPageProps {
} }
export function BlackOpPage({ bladeburner }: BlackOpPageProps): React.ReactElement { export function BlackOpPage({ bladeburner }: BlackOpPageProps): React.ReactElement {
const blackOperations = blackOpsArray.slice(0, bladeburner.numBlackOpsComplete + 1).reverse(); const blackOperations = bladeburner.blackOperationArray.slice(0, bladeburner.numBlackOpsComplete + 1).reverse();
return ( return (
<> <>
@@ -36,11 +36,11 @@ export function BlackOpPage({ bladeburner }: BlackOpPageProps): React.ReactEleme
Unaffected by Charisma. Unaffected by Charisma.
</Typography> </Typography>
{bladeburner.numBlackOpsComplete >= blackOpsArray.length && ( {bladeburner.numBlackOpsComplete >= numberOfBlackOperations && (
<Button <Button
sx={{ my: 1, p: 1 }} sx={{ my: 1, p: 1 }}
onClick={() => { onClick={() => {
if (!Player.bladeburner || Player.bladeburner.numBlackOpsComplete < blackOpsArray.length) { if (!Player.bladeburner || Player.bladeburner.numBlackOpsComplete < numberOfBlackOperations) {
return; return;
} }
finishBitNode(); finishBitNode();
+11 -10
View File
@@ -15,25 +15,26 @@ interface TeamSizeModalProps {
} }
export function TeamSizeModal({ bladeburner, action, open, onClose }: TeamSizeModalProps): React.ReactElement { export function TeamSizeModal({ bladeburner, action, open, onClose }: TeamSizeModalProps): React.ReactElement {
const [teamSize, setTeamSize] = useState<number | undefined>(); const [teamSize, setTeamSize] = useState(0);
function confirmTeamSize(event: React.FormEvent): void { function confirmTeamSize(event: React.FormEvent): void {
// Prevent reloading page when submitting form // Prevent reloading page when submitting form
event.preventDefault(); event.preventDefault();
if (teamSize === undefined) return; if (!Number.isInteger(teamSize) || teamSize < 0) {
const num = Math.round(teamSize); dialogBoxCreate("Invalid value entered for number of Team Members (must be a non-negative integer)");
if (isNaN(num) || num < 0) { return;
dialogBoxCreate("Invalid value entered for number of Team Members (must be numeric and non-negative)");
} else {
action.teamCount = num;
} }
action.teamCount = teamSize;
onClose(); onClose();
} }
function onTeamSize(event: React.ChangeEvent<HTMLInputElement>): void { function onTeamSize(event: React.ChangeEvent<HTMLInputElement>): void {
const x = parseFloat(event.target.value); const newTeamSize = Number(event.target.value);
if (x > bladeburner.teamSize) setTeamSize(bladeburner.teamSize); if (newTeamSize > bladeburner.teamSize) {
else setTeamSize(x); setTeamSize(bladeburner.teamSize);
} else {
setTeamSize(newTeamSize);
}
} }
return ( return (
+1 -1
View File
@@ -77,7 +77,7 @@ export function NetworkDisplayWrapper(): React.ReactElement {
useEffect(() => { useEffect(() => {
const clearSubscription = DarknetEvents.subscribe(() => updateDisplay()); const clearSubscription = DarknetEvents.subscribe(() => updateDisplay());
draggableBackground.current?.addEventListener("wheel", (e) => e.preventDefault()); draggableBackground.current?.addEventListener("wheel", (e) => e.preventDefault(), { passive: false });
scrollTo(DarknetState.netViewTopScroll, DarknetState.netViewLeftScroll); scrollTo(DarknetState.netViewTopScroll, DarknetState.netViewLeftScroll);
updateDisplay(); updateDisplay();
+23 -23
View File
@@ -4,37 +4,37 @@ let pidCounter = 1;
/** Find and return the next available PID for a script */ /** Find and return the next available PID for a script */
export function generateNextPid(): number { export function generateNextPid(): number {
let tempCounter = pidCounter; let pidCandidate = pidCounter;
// Cap the number of search iterations at some arbitrary value to avoid // Cap the number of search iterations at some arbitrary value to avoid
// infinite loops. We'll assume that players wont have 1mil+ running scripts // infinite loops. We'll assume that players won't have a million running scripts.
let found = false; for (let attemptCounter = 0; attemptCounter < 1e6; ++attemptCounter, ++pidCandidate) {
for (let i = 0; i < 1e6; ) { // ensure the candidate PID is a safe integer
if (!workerScripts.has(tempCounter + i)) { if (pidCandidate >= Number.MAX_SAFE_INTEGER) {
found = true; pidCandidate = 1;
tempCounter = tempCounter + i;
break;
} }
// ensure the PID is not in use
if (i === Number.MAX_SAFE_INTEGER - 1) { if (workerScripts.has(pidCandidate)) {
i = 1; continue;
} else {
++i;
} }
// found a PID that's not in use
pidCounter = pidCandidate + 1;
return pidCandidate;
} }
// ran out of attempts without finding an unused PID :-(
if (found) {
pidCounter = tempCounter + 1;
if (pidCounter >= Number.MAX_SAFE_INTEGER) {
pidCounter = 1;
}
return tempCounter;
} else {
return -1; return -1;
} }
}
/**
* Reset the PID counter to 1.
*
* Note that the list of recently finished scripts has to be
* cleared (`recentScripts.splice(0)`) when resetting the PID counter.
* Otherwise scripts which re-use a PID that is still in the
* list of recent scripts do not show up there when they finish
* (if the previous script with that PID is still in the list at that point),
* because the function `AddRecentScript` de-duplicates scripts by PID.
*/
export function resetPidCounter(): void { export function resetPidCounter(): void {
pidCounter = 1; pidCounter = 1;
} }
+7 -6
View File
@@ -16,7 +16,7 @@ import { helpers } from "../Netscript/NetscriptHelpers";
import { getEnumHelper } from "../utils/EnumHelper"; import { getEnumHelper } from "../utils/EnumHelper";
import { Skills } from "../Bladeburner/data/Skills"; import { Skills } from "../Bladeburner/data/Skills";
import { assertStringWithNSContext } from "../Netscript/TypeAssertion"; import { assertStringWithNSContext } from "../Netscript/TypeAssertion";
import { BlackOperations, blackOpsArray } from "../Bladeburner/data/BlackOperations"; import { numberOfBlackOperations } from "../Bladeburner/data/BlackOperations";
import { checkSleeveAPIAccess, checkSleeveNumber } from "../NetscriptFunctions/Sleeve"; import { checkSleeveAPIAccess, checkSleeveNumber } from "../NetscriptFunctions/Sleeve";
import { canAccessBitNodeFeature } from "../BitNode/BitNodeUtils"; import { canAccessBitNodeFeature } from "../BitNode/BitNodeUtils";
import { import {
@@ -81,20 +81,21 @@ export function NetscriptBladeburner(): InternalAPI<INetscriptBladeburner> {
return Object.values(BladeburnerOperationName); return Object.values(BladeburnerOperationName);
}, },
getBlackOpNames: (ctx) => () => { getBlackOpNames: (ctx) => () => {
getBladeburner(ctx); const bladeburner = getBladeburner(ctx);
// Ensures they are sent in the correct order // Ensures they are sent in the correct order
return blackOpsArray.map((blackOp) => blackOp.name); return bladeburner.blackOperationArray.map((blackOp) => blackOp.name);
}, },
getNextBlackOp: (ctx) => () => { getNextBlackOp: (ctx) => () => {
const bladeburner = getBladeburner(ctx); const bladeburner = getBladeburner(ctx);
if (bladeburner.numBlackOpsComplete >= blackOpsArray.length) return null; if (bladeburner.numBlackOpsComplete >= numberOfBlackOperations) return null;
const blackOp = blackOpsArray[bladeburner.numBlackOpsComplete]; const blackOp = bladeburner.blackOperationArray[bladeburner.numBlackOpsComplete];
return { name: blackOp.name, rank: blackOp.reqdRank }; return { name: blackOp.name, rank: blackOp.reqdRank };
}, },
getBlackOpRank: (ctx) => (_blackOpName) => { getBlackOpRank: (ctx) => (_blackOpName) => {
checkBladeburnerAccess(ctx); checkBladeburnerAccess(ctx);
const blackOpName = getEnumHelper("BladeburnerBlackOpName").nsGetMember(ctx, _blackOpName); const blackOpName = getEnumHelper("BladeburnerBlackOpName").nsGetMember(ctx, _blackOpName);
return BlackOperations[blackOpName].reqdRank; const bladeburner = getBladeburner(ctx);
return bladeburner.blackOperations[blackOpName].reqdRank;
}, },
getGeneralActionNames: (ctx) => () => { getGeneralActionNames: (ctx) => () => {
getBladeburner(ctx); getBladeburner(ctx);
+2 -2
View File
@@ -45,7 +45,7 @@ import { ScriptFilePath, resolveScriptFilePath } from "../Paths/ScriptFilePath";
import { getRecordEntries } from "../Types/Record"; import { getRecordEntries } from "../Types/Record";
import { JobTracks } from "../Company/data/JobTracks"; import { JobTracks } from "../Company/data/JobTracks";
import { ServerConstants } from "../Server/data/Constants"; import { ServerConstants } from "../Server/data/Constants";
import { blackOpsArray } from "../Bladeburner/data/BlackOperations"; import { numberOfBlackOperations } from "../Bladeburner/data/BlackOperations";
import { calculateEffectiveRequiredReputation } from "../Company/utils"; import { calculateEffectiveRequiredReputation } from "../Company/utils";
import { addRepToFavor } from "../Faction/formulas/favor"; import { addRepToFavor } from "../Faction/formulas/favor";
import { validBitNodes } from "../BitNode/Constants"; import { validBitNodes } from "../BitNode/Constants";
@@ -1176,7 +1176,7 @@ export function NetscriptSingularity(): InternalAPI<ISingularity> {
if (!Player.bladeburner) { if (!Player.bladeburner) {
return false; return false;
} }
return Player.bladeburner.numBlackOpsComplete >= blackOpsArray.length; return Player.bladeburner.numBlackOpsComplete >= numberOfBlackOperations;
}; };
if (!hackingRequirements() && !bladeburnerRequirements()) { if (!hackingRequirements() && !bladeburnerRequirements()) {
+7 -1
View File
@@ -147,8 +147,14 @@ export abstract class Person implements IPerson {
} }
overrideIntelligence(): void { overrideIntelligence(): void {
// Do not set anything if the player has not unlocked Intelligence. // Reset intelligence data if the player has not unlocked Intelligence.
// Note that this check cannot reset intelligence data in some edge cases (e.g., bitflume from non-BN5 to BN5). This
// is an accepted limitation.
// For more information, please check https://github.com/bitburner-official/bitburner-src/pull/2666
if (Player.sourceFileLvl(5) === 0 && Player.bitNodeN !== 5) { if (Player.sourceFileLvl(5) === 0 && Player.bitNodeN !== 5) {
this.skills.intelligence = 0;
this.exp.intelligence = 0;
this.persistentIntelligenceData.exp = 0;
return; return;
} }
const persistentIntelligenceSkill = this.calculateSkill(this.persistentIntelligenceData.exp, 1); const persistentIntelligenceSkill = this.calculateSkill(this.persistentIntelligenceData.exp, 1);
@@ -135,12 +135,14 @@ export function prestigeAugmentation(this: PlayerObject): void {
this.hp.current = this.hp.max; this.hp.current = this.hp.max;
this.finishWork(true, true); this.finishWork(true, true);
// We need to call overrideIntelligence here instead of prestigeSourceFile to reset intelligence data when installing
// augmentations.
this.overrideIntelligence();
} }
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());
+6 -1
View File
@@ -5,6 +5,11 @@ import { clampNumber } from "../../utils/helpers/clampNumber";
* stat level. Stat-agnostic (same formula for every stat) * stat level. Stat-agnostic (same formula for every stat)
*/ */
export function calculateSkill(exp: number, mult = 1): number { export function calculateSkill(exp: number, mult = 1): number {
// Mult can be 0 in BN12 when the player has a very high SF12 level. In this case, the skill level will never change
// from its initial value (1 for most stats, except intelligence).
if (mult === 0) {
return 1;
}
const value = Math.floor(mult * (32 * Math.log(exp + 534.6) - 200)); const value = Math.floor(mult * (32 * Math.log(exp + 534.6) - 200));
return clampNumber(value, 1); return clampNumber(value, 1);
} }
@@ -12,7 +17,7 @@ export function calculateSkill(exp: number, mult = 1): number {
export function calculateExp(skill: number, mult = 1): number { export function calculateExp(skill: number, mult = 1): number {
const floorSkill = Math.floor(skill); const floorSkill = Math.floor(skill);
let value = Math.exp((skill / mult + 200) / 32) - 534.6; let value = Math.exp((skill / mult + 200) / 32) - 534.6;
if (skill === floorSkill && Number.isFinite(skill)) { if (skill === floorSkill && Number.isFinite(skill) && Number.isFinite(value)) {
// Check for floating point rounding issues that would cause the inverse // Check for floating point rounding issues that would cause the inverse
// operation to return the wrong result. // operation to return the wrong result.
let calcSkill = calculateSkill(value, mult); let calcSkill = calculateSkill(value, mult);
+2
View File
@@ -190,6 +190,8 @@ export function prestigeAugmentation(): void {
} }
} }
// clear recent scripts
recentScripts.splice(0);
resetPidCounter(); resetPidCounter();
ProgramsSeen.clear(); ProgramsSeen.clear();
InvitationsSeen.clear(); InvitationsSeen.clear();
-10
View File
@@ -35,9 +35,6 @@ function giveSourceFile(bitNodeNumber: number): void {
} }
} else { } else {
Player.sourceFiles.set(bitNodeNumber, 1); Player.sourceFiles.set(bitNodeNumber, 1);
if (bitNodeNumber === 5 && Player.skills.intelligence === 0) {
Player.skills.intelligence = 1;
}
dialogBoxCreate( dialogBoxCreate(
<> <>
You received a Source-File for destroying a BitNode! You received a Source-File for destroying a BitNode!
@@ -63,13 +60,6 @@ export function enterBitNode(
if (!isFlume) { if (!isFlume) {
giveSourceFile(destroyedBitNode); giveSourceFile(destroyedBitNode);
} 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;
} }
// Set new Bit Node // Set new Bit Node
Player.bitNodeN = newBitNode; Player.bitNodeN = newBitNode;
+1 -1
View File
@@ -1852,7 +1852,7 @@ export type Task = StudyTask | CompanyWorkTask | CreateProgramWorkTask | CrimeTa
* *
* - All boolean options: false * - All boolean options: false
* *
* If you specify intelligenceOverride, it must be a non-negative integer. * If you specify intelligenceOverride, it must be a positive integer.
* *
* @public * @public
*/ */
+2 -2
View File
@@ -157,7 +157,7 @@ const Engine = {
messages: 150, messages: 150,
mechanicProcess: 5, // Process Bladeburner mechanicProcess: 5, // Process Bladeburner
contractGeneration: 3000, // Generate Coding Contracts contractGeneration: 3000, // Generate Coding Contracts
achievementsCounter: 60, // Check if we have new achievements achievementsCounter: 5, // Check if we have new achievements
}, },
decrementAllCounters: function (numCycles = 1) { decrementAllCounters: function (numCycles = 1) {
@@ -215,7 +215,7 @@ const Engine = {
if (Engine.Counters.achievementsCounter <= 0) { if (Engine.Counters.achievementsCounter <= 0) {
calculateAchievements(); calculateAchievements();
Engine.Counters.achievementsCounter = 300; Engine.Counters.achievementsCounter = 5;
} }
// This **MUST** remain the last block in the function! // This **MUST** remain the last block in the function!
+7 -8
View File
@@ -7,6 +7,7 @@ import { Operation } from "../../../src/Bladeburner/Actions/Operation";
import { import {
AugmentationName, AugmentationName,
BladeburnerActionType, BladeburnerActionType,
BladeburnerBlackOpName,
BladeburnerContractName, BladeburnerContractName,
BladeburnerGeneralActionName, BladeburnerGeneralActionName,
BladeburnerOperationName, BladeburnerOperationName,
@@ -17,15 +18,15 @@ import { FormatsNeedToChange } from "../../../src/ui/formatNumber";
import { CrimeWork } from "../../../src/Work/CrimeWork"; import { CrimeWork } from "../../../src/Work/CrimeWork";
import type { Action, ActionIdentifier } from "../../../src/Bladeburner/Types"; import type { Action, ActionIdentifier } from "../../../src/Bladeburner/Types";
import type { Skills } from "@nsdefs"; import type { Skills } from "@nsdefs";
import { BlackOperations } from "../../../src/Bladeburner/data/BlackOperations";
import { applyAugmentation } from "../../../src/Augmentation/AugmentationHelpers"; import { applyAugmentation } from "../../../src/Augmentation/AugmentationHelpers";
import { PlayerOwnedAugmentation } from "../../../src/Augmentation/PlayerOwnedAugmentation"; import { PlayerOwnedAugmentation } from "../../../src/Augmentation/PlayerOwnedAugmentation";
import { BlackOperation } from "../../../src/Bladeburner/Actions/BlackOperation";
describe("Bladeburner Actions", () => { describe("Bladeburner Actions", () => {
const SampleContract = Contract.createId(BladeburnerContractName.Tracking); const SampleContract = Contract.createId(BladeburnerContractName.Tracking);
const SampleGeneralAction = GeneralAction.createId(BladeburnerGeneralActionName.Diplomacy); const SampleGeneralAction = GeneralAction.createId(BladeburnerGeneralActionName.Diplomacy);
const SampleOperation = Operation.createId(BladeburnerOperationName.Assassination); const SampleOperation = Operation.createId(BladeburnerOperationName.Assassination);
const SampleBlackOp = BlackOperations["Operation Centurion"].id; const SampleBlackOp = BlackOperation.createId(BladeburnerBlackOpName.OperationCenturion);
const ENOUGH_TIME_TO_FINISH_ACTION = 1e5; const ENOUGH_TIME_TO_FINISH_ACTION = 1e5;
const BASE_STAT_EXP = 1e6; const BASE_STAT_EXP = 1e6;
@@ -37,7 +38,8 @@ describe("Bladeburner Actions", () => {
const contracts = Object.values(new Bladeburner().contracts); const contracts = Object.values(new Bladeburner().contracts);
const operations = Object.values(new Bladeburner().operations); const operations = Object.values(new Bladeburner().operations);
const nonGeneralActions = [contracts, operations, Object.values(BlackOperations)].flat(); const blackOperations = Object.values(new Bladeburner().blackOperations);
const nonGeneralActions = [contracts, operations, blackOperations].flat();
describe("Without Simulacrum", () => { describe("Without Simulacrum", () => {
it("Starting an action cancels player's work immediately", () => { it("Starting an action cancels player's work immediately", () => {
@@ -139,16 +141,13 @@ describe("Bladeburner Actions", () => {
}); });
}); });
describe.each([SampleContract, SampleOperation, BlackOperations["Operation Archangel"].id])( describe.each([SampleContract, SampleOperation, SampleBlackOp])("non-general actions increase rank", (id) => {
"non-general actions increase rank",
(id) => {
it(`${id.type}`, () => { it(`${id.type}`, () => {
before = bb.rank; before = bb.rank;
complete(id, forceSuccess); complete(id, forceSuccess);
expect(bb.rank).toBeGreaterThan(before); expect(bb.rank).toBeGreaterThan(before);
}); });
}, });
);
describe("non-general actions increase rank", () => { describe("non-general actions increase rank", () => {
let beforeMinor, minorGain, beforeMajor, majorGain; let beforeMinor, minorGain, beforeMajor, majorGain;
+48 -6
View File
@@ -1,5 +1,4 @@
import { Player, setPlayer } from "@player"; import { Player, setPlayer } from "@player";
import { FormatsNeedToChange } from "../../../src/ui/formatNumber";
import type { ActionIdFor } from "../../../src/Bladeburner/Types"; import type { ActionIdFor } from "../../../src/Bladeburner/Types";
import type { Bladeburner } from "../../../src/Bladeburner/Bladeburner"; import type { Bladeburner } from "../../../src/Bladeburner/Bladeburner";
import { BlackOperation } from "../../../src/Bladeburner/Actions/BlackOperation"; import { BlackOperation } from "../../../src/Bladeburner/Actions/BlackOperation";
@@ -9,6 +8,9 @@ import { SleeveSupportWork } from "../../../src/PersonObjects/Sleeve/Work/Sleeve
import { BladeburnerBlackOpName, BladeburnerContractName, BladeburnerOperationName } from "@enums"; import { BladeburnerBlackOpName, BladeburnerContractName, BladeburnerOperationName } from "@enums";
import { PlayerObject } from "../../../src/PersonObjects/Player/PlayerObject"; import { PlayerObject } from "../../../src/PersonObjects/Player/PlayerObject";
import { recalculateNumberOfOwnedSleeves } from "../../../src/PersonObjects/Sleeve/SleeveCovenantPurchases"; import { recalculateNumberOfOwnedSleeves } from "../../../src/PersonObjects/Sleeve/SleeveCovenantPurchases";
import { initGameEnvironment } from "../Utilities";
initGameEnvironment();
/** /**
* You may want to use hook to help with debugging * You may want to use hook to help with debugging
@@ -27,11 +29,6 @@ describe("Bladeburner Team", () => {
let inst: Bladeburner; let inst: Bladeburner;
let action: BlackOperation | Operation; let action: BlackOperation | Operation;
beforeAll(() => {
/* Initialise Formatters. Dependency of Bladeburner */
FormatsNeedToChange.emit();
});
beforeEach(() => { beforeEach(() => {
setPlayer(new PlayerObject()); setPlayer(new PlayerObject());
Player.init(); Player.init();
@@ -133,6 +130,51 @@ describe("Bladeburner Team", () => {
}); });
}); });
describe("Check teamSize and teamCount", () => {
test("Failed action", () => {
teamSize(10);
startAction(OP);
forceMaxCasualties();
for (const action of [...Object.values(inst.operations), ...Object.values(inst.blackOperations)]) {
action.teamCount = 10;
expect(action.teamCount).toStrictEqual(10);
}
actionFails();
expect(inst.teamSize).toStrictEqual(0);
for (const action of [...Object.values(inst.operations), ...Object.values(inst.blackOperations)]) {
expect(action.teamCount).toStrictEqual(0);
}
});
test("Sleeves", () => {
teamSize(1);
supportingSleeves(8);
expect(inst.teamSize).toStrictEqual(9);
startAction(OP);
forceMaxCasualties();
for (const action of [...Object.values(inst.operations), ...Object.values(inst.blackOperations)]) {
action.teamCount = 9;
expect(action.teamCount).toStrictEqual(9);
}
actionFails();
// The teamCount of all operations/black operations should be 8, not 0.
assertSleevesHaveBeenShocked();
expect(inst.teamSize).toStrictEqual(8);
for (const action of [...Object.values(inst.operations), ...Object.values(inst.blackOperations)]) {
expect(action.teamCount).toStrictEqual(8);
}
Player.sleeves[0].stopWork();
expect(inst.teamSize).toStrictEqual(7);
for (const action of [...Object.values(inst.operations), ...Object.values(inst.blackOperations)]) {
expect(action.teamCount).toStrictEqual(7);
}
Player.sleeves[0].startWork(new SleeveSupportWork());
expect(inst.teamSize).toStrictEqual(8);
for (const action of [...Object.values(inst.operations), ...Object.values(inst.blackOperations)]) {
expect(action.teamCount).toStrictEqual(7);
}
});
});
function teamSize(n: number) { function teamSize(n: number) {
inst.teamSize = n; inst.teamSize = n;
} }
+200 -2
View File
@@ -1,5 +1,4 @@
import { installAugmentations } from "../../../src/Augmentation/AugmentationHelpers"; import { installAugmentations } from "../../../src/Augmentation/AugmentationHelpers";
import { blackOpsArray } from "../../../src/Bladeburner/data/BlackOperations";
import { AugmentationName, CompanyName, CompletedProgramName, FactionName, JobField, JobName } from "@enums"; import { AugmentationName, CompanyName, CompletedProgramName, FactionName, JobField, JobName } from "@enums";
import { Player } from "@player"; import { Player } from "@player";
import { prestigeSourceFile } from "../../../src/Prestige"; import { prestigeSourceFile } from "../../../src/Prestige";
@@ -14,6 +13,7 @@ import { Companies } from "../../../src/Company/Companies";
import { CompanyPositions } from "../../../src/Company/CompanyPositions"; 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";
import { numberOfBlackOperations } from "../../../src/Bladeburner/data/BlackOperations";
const nextBN = 4; const nextBN = 4;
@@ -163,6 +163,30 @@ function testIntelligenceOverride(
expect(Player.persistentIntelligenceData.exp).toStrictEqual(1e6 + intelligenceExpGainOnPrestige * 2); expect(Player.persistentIntelligenceData.exp).toStrictEqual(1e6 + intelligenceExpGainOnPrestige * 2);
} }
/** Sets intelligence exp while bypassing the requirements (SF5 or being in BN5). */
function manuallySetIntelligenceExp(exp: number): void {
Player.exp.intelligence = exp;
Player.skills.intelligence = Math.floor(Player.calculateSkill(Player.exp.intelligence, 1));
Player.persistentIntelligenceData.exp = exp;
}
function expectIntelligenceExp(exp: number): void {
expect(Player.exp.intelligence).toStrictEqual(exp);
expect(Player.skills.intelligence).toStrictEqual(Math.floor(Player.calculateSkill(exp, 1)));
expect(Player.persistentIntelligenceData.exp).toStrictEqual(exp);
}
/**
* This function is not equivalent to expectIntelligenceExp(0). The intelligence skill level starts at 0, not 1 like
* other stats. This function specifically verifies the initial state of intelligence data (before entering BN5).
*/
function expectInitialIntelligenceData(): void {
expect(Player.exp.intelligence).toStrictEqual(0);
// The intelligence skill level starts at 0.
expect(Player.skills.intelligence).toStrictEqual(0);
expect(Player.persistentIntelligenceData.exp).toStrictEqual(0);
}
function setUpBeforeDestroyingWD(): void { function setUpBeforeDestroyingWD(): void {
Player.queueAugmentation(AugmentationName.TheRedPill); Player.queueAugmentation(AugmentationName.TheRedPill);
installAugmentations(); installAugmentations();
@@ -170,7 +194,7 @@ function setUpBeforeDestroyingWD(): void {
const wdServer = GetServerOrThrow(SpecialServers.WorldDaemon); const wdServer = GetServerOrThrow(SpecialServers.WorldDaemon);
wdServer.hasAdminRights = true; wdServer.hasAdminRights = true;
Player.startBladeburner(); Player.startBladeburner();
setNumBlackOpsComplete(blackOpsArray.length); setNumBlackOpsComplete(numberOfBlackOperations);
} }
describe("b1tflum3", () => { describe("b1tflum3", () => {
@@ -646,3 +670,177 @@ describe("purchaseProgram", () => {
}); });
}); });
}); });
describe("Intelligence", () => {
beforeEach(() => {
setupBasicTestingEnvironment();
expect(Player.bitNodeN).toStrictEqual(1);
expect(Player.sourceFileLvl(5)).toStrictEqual(0);
expectInitialIntelligenceData();
});
test("Get SF5", () => {
// This is the most common scenario. Some checks in this test will be repeated in other tests.
const ns = getNS();
ns.singularity.b1tflum3(5);
expectIntelligenceExp(0);
Player.gainIntelligenceExp(1000);
expectIntelligenceExp(1000);
setUpBeforeDestroyingWD();
ns.singularity.destroyW0r1dD43m0n(5);
expectIntelligenceExp(1300);
expect(Player.sourceFileLvl(5)).toStrictEqual(1);
setUpBeforeDestroyingWD();
ns.singularity.destroyW0r1dD43m0n(5);
expectIntelligenceExp(1600);
expect(Player.sourceFileLvl(5)).toStrictEqual(2);
setUpBeforeDestroyingWD();
ns.singularity.destroyW0r1dD43m0n(1);
expectIntelligenceExp(1900);
expect(Player.sourceFileLvl(5)).toStrictEqual(3);
});
test("Can gain intelligence exp with SF5", () => {
Player.sourceFiles.set(5, 1);
const ns = getNS();
ns.singularity.b1tflum3(1);
expectIntelligenceExp(0);
Player.gainIntelligenceExp(1000);
expectIntelligenceExp(1000);
ns.singularity.b1tflum3(1);
expectIntelligenceExp(1000);
setUpBeforeDestroyingWD();
ns.singularity.destroyW0r1dD43m0n(1);
expectIntelligenceExp(1300);
});
test("Can gain intelligence exp in BN5", () => {
const ns = getNS();
ns.singularity.b1tflum3(5);
expectIntelligenceExp(0);
Player.gainIntelligenceExp(1000);
expectIntelligenceExp(1000);
});
describe("Reset intelligence data", () => {
test("Install augmentations", () => {
manuallySetIntelligenceExp(50);
expectIntelligenceExp(50);
Player.queueAugmentation(AugmentationName.Targeting1);
expect(installAugmentations()).toStrictEqual(true);
expectInitialIntelligenceData();
});
test("Bitflume", () => {
const ns = getNS();
// Bitflume from non-BN5 to non-BN5.
expect(Player.bitNodeN).toStrictEqual(1);
manuallySetIntelligenceExp(50);
expectIntelligenceExp(50);
ns.singularity.b1tflum3(1);
// Reset intelligence data
expectInitialIntelligenceData();
// We intentionally skip this scenario.
// For more information, please check https://github.com/bitburner-official/bitburner-src/pull/2666
// // Bitflume from non-BN5 to BN5.
// expect(Player.bitNodeN).toStrictEqual(1);
// manuallySetIntelligenceExp(50);
// expectIntelligenceExp(50);
// ns.singularity.b1tflum3(5);
// // Reset intelligence data and skill = 1
// expectIntelligenceExp(0);
// Bitflume from non-BN5 to BN5.
ns.singularity.b1tflum3(5);
// Check if skill is set to 1.
expectIntelligenceExp(0);
// Bitflume from BN5 to BN5.
expect(Player.bitNodeN).toStrictEqual(5);
Player.gainIntelligenceExp(50);
expectIntelligenceExp(50);
// Bitflume to BN5 again.
ns.singularity.b1tflum3(5);
// Not lose exp when bitfluming from BN5 to BN5.
expectIntelligenceExp(50);
// Bitflume from BN5 to non-BN5.
expect(Player.bitNodeN).toStrictEqual(5);
Player.gainIntelligenceExp(50);
// 50 exp from the previous scenario + 50 exp from this scenario.
expectIntelligenceExp(100);
ns.singularity.b1tflum3(1);
// Reset intelligence data
expectInitialIntelligenceData();
});
test("Destroy WD", () => {
const ns = getNS();
// Destroy WD of non-BN5 and jump to non-BN5.
expect(Player.bitNodeN).toStrictEqual(1);
manuallySetIntelligenceExp(50);
expectIntelligenceExp(50);
setUpBeforeDestroyingWD();
ns.singularity.destroyW0r1dD43m0n(1);
// Reset intelligence data
expectInitialIntelligenceData();
// We intentionally skip this scenario.
// For more information, please check https://github.com/bitburner-official/bitburner-src/pull/2666
// // Destroy WD of non-BN5 and jump to BN5.
// expect(Player.bitNodeN).toStrictEqual(1);
// manuallySetIntelligenceExp(50);
// expectIntelligenceExp(50);
// setUpBeforeDestroyingWD();
// ns.singularity.destroyW0r1dD43m0n(5);
// // Reset intelligence data and skill = 1
// expectIntelligenceExp(0);
// Destroy WD of BN5 and jump to BN5.
ns.singularity.b1tflum3(5);
// Check the initial state that we want to test: in BN5 and do not have SF5.
expect(Player.bitNodeN).toStrictEqual(5);
expect(Player.sourceFileLvl(5)).toStrictEqual(0);
// Check if skill is set to 1.
expectIntelligenceExp(0);
Player.gainIntelligenceExp(50);
expectIntelligenceExp(50);
setUpBeforeDestroyingWD();
ns.singularity.destroyW0r1dD43m0n(5);
// 50 exp from Player.gainIntelligenceExp() + 300 exp reward of destroying WD.
expectIntelligenceExp(350);
// Destroy WD of BN5 and jump to non-BN5.
Player.gainIntelligenceExp(50);
// 350 exp from the previous scenario + 50 exp from Player.gainIntelligenceExp().
expectIntelligenceExp(400);
setUpBeforeDestroyingWD();
ns.singularity.destroyW0r1dD43m0n(1);
expectIntelligenceExp(700);
});
});
test("Cannot gain intelligence exp without SF5 or being in BN5", () => {
const ns = getNS();
Player.gainIntelligenceExp(1000);
expectInitialIntelligenceData();
ns.singularity.b1tflum3(1);
expectInitialIntelligenceData();
setUpBeforeDestroyingWD();
ns.singularity.destroyW0r1dD43m0n(1);
expectInitialIntelligenceData();
});
test("Cannot gain intelligence exp even with intelligence skill > 0", () => {
manuallySetIntelligenceExp(50);
expectIntelligenceExp(50);
Player.gainIntelligenceExp(1000);
expectIntelligenceExp(50);
});
});
+129 -1
View File
@@ -75,6 +75,7 @@ exports[`Check Save File Continuity PlayerSave continuity 1`] = `
"bladeburner": { "bladeburner": {
"ctor": "Bladeburner", "ctor": "Bladeburner",
"data": { "data": {
"_teamSize": 0,
"action": null, "action": null,
"actionTimeCurrent": 0, "actionTimeCurrent": 0,
"actionTimeOverflow": 0, "actionTimeOverflow": 0,
@@ -84,6 +85,134 @@ exports[`Check Save File Continuity PlayerSave continuity 1`] = `
"automateEnabled": false, "automateEnabled": false,
"automateThreshHigh": 0, "automateThreshHigh": 0,
"automateThreshLow": 0, "automateThreshLow": 0,
"blackOperations": {
"Operation Annihilus": {
"ctor": "BlackOperation",
"data": {
"teamCount": 0,
},
},
"Operation Archangel": {
"ctor": "BlackOperation",
"data": {
"teamCount": 0,
},
},
"Operation Ares": {
"ctor": "BlackOperation",
"data": {
"teamCount": 0,
},
},
"Operation Centurion": {
"ctor": "BlackOperation",
"data": {
"teamCount": 0,
},
},
"Operation Daedalus": {
"ctor": "BlackOperation",
"data": {
"teamCount": 0,
},
},
"Operation Deckard": {
"ctor": "BlackOperation",
"data": {
"teamCount": 0,
},
},
"Operation Hyron": {
"ctor": "BlackOperation",
"data": {
"teamCount": 0,
},
},
"Operation Ion Storm": {
"ctor": "BlackOperation",
"data": {
"teamCount": 0,
},
},
"Operation Juggernaut": {
"ctor": "BlackOperation",
"data": {
"teamCount": 0,
},
},
"Operation K": {
"ctor": "BlackOperation",
"data": {
"teamCount": 0,
},
},
"Operation Morpheus": {
"ctor": "BlackOperation",
"data": {
"teamCount": 0,
},
},
"Operation Red Dragon": {
"ctor": "BlackOperation",
"data": {
"teamCount": 0,
},
},
"Operation Shoulder of Orion": {
"ctor": "BlackOperation",
"data": {
"teamCount": 0,
},
},
"Operation Titan": {
"ctor": "BlackOperation",
"data": {
"teamCount": 0,
},
},
"Operation Typhoon": {
"ctor": "BlackOperation",
"data": {
"teamCount": 0,
},
},
"Operation Tyrell": {
"ctor": "BlackOperation",
"data": {
"teamCount": 0,
},
},
"Operation Ultron": {
"ctor": "BlackOperation",
"data": {
"teamCount": 0,
},
},
"Operation Vindictus": {
"ctor": "BlackOperation",
"data": {
"teamCount": 0,
},
},
"Operation Wallace": {
"ctor": "BlackOperation",
"data": {
"teamCount": 0,
},
},
"Operation X": {
"ctor": "BlackOperation",
"data": {
"teamCount": 0,
},
},
"Operation Zero": {
"ctor": "BlackOperation",
"data": {
"teamCount": 0,
},
},
},
"cities": { "cities": {
"Aevum": { "Aevum": {
"ctor": "City", "ctor": "City",
@@ -281,7 +410,6 @@ exports[`Check Save File Continuity PlayerSave continuity 1`] = `
"staminaBonus": 0, "staminaBonus": 0,
"storedCycles": 0, "storedCycles": 0,
"teamLost": 0, "teamLost": 0,
"teamSize": 0,
"totalSkillPoints": 666, "totalSkillPoints": 666,
}, },
}, },