mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2026-04-17 06:48:42 +02:00
BUGFIX: Fix webstorm by using a mutationLock (#2542)
This commit is contained in:
@@ -133,7 +133,7 @@ export const clearDarknet = () => {
|
||||
DarknetState.zoomIndex = 7;
|
||||
DarknetState.netViewLeftScroll = 0;
|
||||
DarknetState.netViewTopScroll = 0;
|
||||
DarknetState.allowMutating = true;
|
||||
DarknetState.mutationLock?.();
|
||||
DarknetState.openServer = null;
|
||||
DarknetState.stockPromotions = {};
|
||||
DarknetState.migrationInductionServers = new Map();
|
||||
|
||||
@@ -43,7 +43,7 @@ export const processDarknet = (cycles: number): void => {
|
||||
};
|
||||
|
||||
export const mutateDarknet = (): void => {
|
||||
if (!DarknetState.allowMutating) {
|
||||
if (DarknetState.mutationLock) {
|
||||
return;
|
||||
}
|
||||
const servers = getAllMovableDarknetServers();
|
||||
|
||||
@@ -20,42 +20,62 @@ const validateDarknetNetworkAndEmitDarknetEvent = (): void => {
|
||||
DarknetEvents.emit();
|
||||
};
|
||||
|
||||
const cancelled = Symbol("cancelled");
|
||||
|
||||
export const launchWebstorm = async (suppressToast = false) => {
|
||||
DarknetState.allowMutating = false;
|
||||
if (!suppressToast) {
|
||||
SnackbarEvents.emit(`DARKNET WEBSTORM APPROACHING`, ToastVariant.ERROR, 5000);
|
||||
if (DarknetState.mutationLock) {
|
||||
return;
|
||||
}
|
||||
await sleep(5000);
|
||||
try {
|
||||
const cancelPromise = new Promise((res, rej) => {
|
||||
DarknetState.mutationLock = () => {
|
||||
rej(cancelled);
|
||||
DarknetState.mutationLock = null;
|
||||
};
|
||||
});
|
||||
const cancellableSleep = (ms: number) => Promise.race([sleep(ms), cancelPromise]);
|
||||
|
||||
const serversToDelete = getAllMovableDarknetServers().length * 0.6 + (Math.random() * getNetDepth() - 6);
|
||||
deleteRandomDarknetServers(serversToDelete);
|
||||
moveRandomDarknetServers((getAllMovableDarknetServers().length - serversToDelete) * 0.6);
|
||||
restartAllDarknetServers();
|
||||
validateDarknetNetworkAndEmitDarknetEvent();
|
||||
triggerNextUpdate();
|
||||
if (!suppressToast) {
|
||||
SnackbarEvents.emit(`DARKNET WEBSTORM APPROACHING`, ToastVariant.ERROR, 5000);
|
||||
}
|
||||
await cancellableSleep(5000);
|
||||
|
||||
await sleep(4000);
|
||||
addRandomDarknetServers(NET_WIDTH);
|
||||
validateDarknetNetworkAndEmitDarknetEvent();
|
||||
triggerNextUpdate();
|
||||
const serversToDelete = getAllMovableDarknetServers().length * 0.6 + (Math.random() * getNetDepth() - 6);
|
||||
deleteRandomDarknetServers(serversToDelete);
|
||||
moveRandomDarknetServers((getAllMovableDarknetServers().length - serversToDelete) * 0.6);
|
||||
restartAllDarknetServers();
|
||||
validateDarknetNetworkAndEmitDarknetEvent();
|
||||
triggerNextUpdate();
|
||||
|
||||
await sleep(4000);
|
||||
addRandomDarknetServers(NET_WIDTH * 2);
|
||||
validateDarknetNetworkAndEmitDarknetEvent();
|
||||
triggerNextUpdate();
|
||||
await cancellableSleep(4000);
|
||||
addRandomDarknetServers(NET_WIDTH);
|
||||
validateDarknetNetworkAndEmitDarknetEvent();
|
||||
triggerNextUpdate();
|
||||
|
||||
await sleep(4000);
|
||||
addRandomDarknetServers(NET_WIDTH * 2);
|
||||
validateDarknetNetworkAndEmitDarknetEvent();
|
||||
triggerNextUpdate();
|
||||
await cancellableSleep(4000);
|
||||
addRandomDarknetServers(NET_WIDTH * 2);
|
||||
validateDarknetNetworkAndEmitDarknetEvent();
|
||||
triggerNextUpdate();
|
||||
|
||||
await sleep(8000);
|
||||
balanceDarknetServers();
|
||||
validateDarknetNetworkAndEmitDarknetEvent();
|
||||
triggerNextUpdate();
|
||||
await cancellableSleep(4000);
|
||||
addRandomDarknetServers(NET_WIDTH * 2);
|
||||
validateDarknetNetworkAndEmitDarknetEvent();
|
||||
triggerNextUpdate();
|
||||
|
||||
await sleep(5000);
|
||||
DarknetState.allowMutating = true;
|
||||
await cancellableSleep(8000);
|
||||
balanceDarknetServers();
|
||||
validateDarknetNetworkAndEmitDarknetEvent();
|
||||
triggerNextUpdate();
|
||||
|
||||
await cancellableSleep(5000);
|
||||
} catch (ex) {
|
||||
if (ex !== cancelled) throw ex;
|
||||
} finally {
|
||||
// Maybe a future TypeScript will remove the need for this...
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const dumb = DarknetState.mutationLock!;
|
||||
dumb?.();
|
||||
}
|
||||
};
|
||||
|
||||
export const handleStormSeed = (server: BaseServer) => {
|
||||
|
||||
@@ -151,7 +151,7 @@ const decorateName = (name: string): string => {
|
||||
// Just in case we hit a lot of the same name mutations, or if the player
|
||||
// messes with Math.random(), prevent an infinite loop
|
||||
updatedName += `/T${Date.now()}`;
|
||||
break;
|
||||
continue;
|
||||
}
|
||||
|
||||
const connector = connectors[Math.floor(Math.random() * connectors.length)];
|
||||
|
||||
@@ -9,7 +9,7 @@ import type { PasswordResponse } from "./DarknetServerOptions";
|
||||
import { assertFiniteNumber, assertNonNullish } from "../../utils/TypeAssertion";
|
||||
|
||||
/** Event emitter to allow the UI to subscribe to Darknet gameplay updates in order to trigger rerenders properly */
|
||||
export const DarknetEvents = new EventEmitter();
|
||||
export const DarknetEvents = new EventEmitter<[]>();
|
||||
|
||||
export type ServerState = {
|
||||
lastLogTime?: Date;
|
||||
@@ -26,7 +26,10 @@ export type LogEntry = {
|
||||
* If you add a new property to this global state, you must check if you need to reset it in prestigeDarknetState.
|
||||
*/
|
||||
export const DarknetState = {
|
||||
allowMutating: true,
|
||||
// If this is null, network mutation is allowed. If this is a function,
|
||||
// mutation is frozen and calling the function releases the lock.
|
||||
// *Only* the lock function may reset this to null.
|
||||
mutationLock: null as (() => void) | null,
|
||||
openServer: null as BaseServer | null,
|
||||
nextMutation: Promise.resolve(),
|
||||
nextMutationResolver: null as (() => void) | null,
|
||||
@@ -77,7 +80,7 @@ export const DarknetState = {
|
||||
};
|
||||
|
||||
export function prestigeDarknetState(prestigeSourceFile: boolean): void {
|
||||
DarknetState.allowMutating = true;
|
||||
DarknetState.mutationLock?.();
|
||||
DarknetState.openServer = null;
|
||||
DarknetState.storedCycles = 0;
|
||||
if (prestigeSourceFile) {
|
||||
|
||||
@@ -243,7 +243,11 @@ export function NetworkDisplayWrapper(): React.ReactElement {
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
{DarknetState.allowMutating ? (
|
||||
{DarknetState.mutationLock ? (
|
||||
<Typography variant={"h6"} className={classes.gold}>
|
||||
[WEBSTORM WARNING]
|
||||
</Typography>
|
||||
) : (
|
||||
<Box className={`${classes.inlineFlexBox}`}>
|
||||
<Typography variant={"h5"} sx={{ fontWeight: "bold" }}>
|
||||
Dark Net
|
||||
@@ -265,10 +269,6 @@ export function NetworkDisplayWrapper(): React.ReactElement {
|
||||
</Tooltip>
|
||||
)}
|
||||
</Box>
|
||||
) : (
|
||||
<Typography variant={"h6"} className={classes.gold}>
|
||||
[WEBSTORM WARNING]
|
||||
</Typography>
|
||||
)}
|
||||
|
||||
<div
|
||||
|
||||
@@ -67,7 +67,7 @@ export const PasswordPrompt = ({ server, onClose }: PasswordPromptProps): React.
|
||||
setLastDarknetResultFromAuth(authResult.result);
|
||||
|
||||
if (authResult.result.success) {
|
||||
DarknetEvents.emit("server-unlocked", server);
|
||||
DarknetEvents.emit();
|
||||
} else {
|
||||
// This selects the text inside the password input field so that the player can immediately start typing a new
|
||||
// guess without needing to clear out the old one.
|
||||
|
||||
@@ -7,10 +7,12 @@ import type { IPAddress } from "../Types/strings";
|
||||
export const createRandomIp = (): IPAddress => {
|
||||
// Credit goes to yichizhng on BitBurner discord
|
||||
// Generates a number like 0.c8f0a07f1d47e8
|
||||
const ip = Math.random().toString(16);
|
||||
// The extra zeros are to catch numbers like 0 and 0.5 that have a shorter
|
||||
// exact representation.
|
||||
const ip = Math.random().toString(16) + "000000000";
|
||||
// uses regex to match every 2 characters. [0.][c8][f0][a0][7f][1d][47][e8]
|
||||
// we only want #1 through #4
|
||||
const matchResult = ip.match(/../g);
|
||||
const matchResult = ip.match(/..?/g);
|
||||
if (!matchResult) {
|
||||
// This case should never happen.
|
||||
throw new Error(`Unexpected regex matching bug in createRandomIp. ip: ${ip}`);
|
||||
|
||||
@@ -62,6 +62,8 @@ import { isDirectoryPath } from "../../../src/Paths/Directory";
|
||||
import { isFilePath } from "../../../src/Paths/FilePath";
|
||||
import { LAB_CACHE_NAME } from "../../../src/DarkNet/effects/labyrinth";
|
||||
import { generateCacheFilename } from "../../../src/DarkNet/effects/cacheFiles";
|
||||
import { getAllDarknetServers } from "../../../src/DarkNet/utils/darknetNetworkUtils";
|
||||
import { prestigeAugmentation } from "../../../src/Prestige";
|
||||
|
||||
beforeAll(() => {
|
||||
initGameEnvironment();
|
||||
@@ -691,6 +693,11 @@ describe("Password Tests", () => {
|
||||
});
|
||||
});
|
||||
|
||||
const serverSnapshot = () =>
|
||||
getAllDarknetServers()
|
||||
.map((s) => ({ hostname: s.hostname, depth: s.depth, leftOffset: s.leftOffset }))
|
||||
.sort((a, b) => (a.hostname < b.hostname ? -1 : a.hostname === b.hostname ? 0 : 1));
|
||||
|
||||
describe("mutateDarknet and webstorm", () => {
|
||||
test("mutateDarknet", () => {
|
||||
const spiedExceptionAlert = jest.spyOn(exceptionAlertModule, "exceptionAlert");
|
||||
@@ -711,6 +718,61 @@ describe("mutateDarknet and webstorm", () => {
|
||||
expect(spiedExceptionAlert).not.toHaveBeenCalled();
|
||||
expect(spiedConsoleError).not.toHaveBeenCalled();
|
||||
});
|
||||
test("mutation during webstorm", async () => {
|
||||
const realRandom = Math.random;
|
||||
try {
|
||||
jest.useFakeTimers();
|
||||
const promise = launchWebstorm();
|
||||
expect(DarknetState.mutationLock).toBeTruthy();
|
||||
|
||||
await jest.advanceTimersByTimeAsync(10000);
|
||||
expect(DarknetState.mutationLock).toBeTruthy();
|
||||
|
||||
const initialServers = serverSnapshot();
|
||||
// Low rolls cause stuff to happen, we want deterministic testing.
|
||||
// Jest spies keep state, make our own mock so it doesn't eat memory in
|
||||
// case something goes wrong.
|
||||
let count = 0;
|
||||
Math.random = () => count++ * (Number.EPSILON * 1024);
|
||||
mutateDarknet();
|
||||
expect(serverSnapshot()).toEqual(initialServers);
|
||||
|
||||
await jest.runAllTimersAsync();
|
||||
await promise; // Should immediately finish
|
||||
expect(DarknetState.mutationLock).toBeNull();
|
||||
|
||||
mutateDarknet();
|
||||
expect(serverSnapshot()).not.toEqual(initialServers);
|
||||
} finally {
|
||||
jest.useRealTimers();
|
||||
Math.random = realRandom;
|
||||
}
|
||||
});
|
||||
test("prestige during webstorm", async () => {
|
||||
try {
|
||||
jest.useFakeTimers();
|
||||
const promise = launchWebstorm();
|
||||
await jest.advanceTimersByTimeAsync(0); // Finish any promises
|
||||
expect(DarknetState.mutationLock).toBeTruthy();
|
||||
|
||||
const beforePrestige = serverSnapshot();
|
||||
prestigeAugmentation();
|
||||
|
||||
expect(DarknetState.mutationLock).toBeNull();
|
||||
const initialServers = serverSnapshot();
|
||||
// Validate that prestige changed the network
|
||||
expect(initialServers).not.toEqual(beforePrestige);
|
||||
|
||||
await jest.runAllTimersAsync();
|
||||
await promise; // Should immediately finish
|
||||
|
||||
expect(DarknetState.mutationLock).toBeNull();
|
||||
// Webstorm should not have changed anything
|
||||
expect(serverSnapshot()).toEqual(initialServers);
|
||||
} finally {
|
||||
jest.useRealTimers();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function validatePath(hostname: string): void {
|
||||
|
||||
@@ -116,11 +116,11 @@ describe("Common APIs", () => {
|
||||
const ns = getNsOnNonDarkwebDarknetServer();
|
||||
const result1 = ns.dnet.unleashStormSeed();
|
||||
expect(result1.success).toStrictEqual(false);
|
||||
expect(DarknetState.allowMutating).toStrictEqual(true);
|
||||
expect(DarknetState.mutationLock).toBeNull();
|
||||
getDarknetServerOrThrow(ns.getHostname()).programs.push(CompletedProgramName.stormSeed);
|
||||
const result2 = ns.dnet.unleashStormSeed();
|
||||
expect(result2.success).toStrictEqual(true);
|
||||
expect(DarknetState.allowMutating).toStrictEqual(false);
|
||||
expect(DarknetState.mutationLock).toBeTruthy();
|
||||
});
|
||||
test("getDarknetInstability", () => {
|
||||
const ns = getNsOnDarkWeb();
|
||||
|
||||
Reference in New Issue
Block a user