mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2026-04-20 00:04:22 +02:00
Compare commits
8 Commits
c21d1f44b2
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
| 2aa5092d85 | |||
| a7409a01cc | |||
| a99109d9c7 | |||
| 95af138c39 | |||
| f5bbc26495 | |||
| f8ec7f4294 | |||
| c06c6590c9 | |||
| 45bce6e45e |
@@ -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:**
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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];
|
||||||
|
|||||||
@@ -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];
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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 (
|
||||||
|
|||||||
@@ -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
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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()) {
|
||||||
|
|||||||
@@ -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());
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -190,6 +190,8 @@ export function prestigeAugmentation(): void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// clear recent scripts
|
||||||
|
recentScripts.splice(0);
|
||||||
resetPidCounter();
|
resetPidCounter();
|
||||||
ProgramsSeen.clear();
|
ProgramsSeen.clear();
|
||||||
InvitationsSeen.clear();
|
InvitationsSeen.clear();
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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,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;
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -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,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user