BUGFIX: Fix webstorm by using a mutationLock (#2542)

This commit is contained in:
David Walker
2026-03-06 11:11:06 -08:00
committed by GitHub
parent 90f6db6d24
commit b6a29681f4
10 changed files with 131 additions and 44 deletions

View File

@@ -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();

View File

@@ -43,7 +43,7 @@ export const processDarknet = (cycles: number): void => {
};
export const mutateDarknet = (): void => {
if (!DarknetState.allowMutating) {
if (DarknetState.mutationLock) {
return;
}
const servers = getAllMovableDarknetServers();

View File

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

View File

@@ -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)];

View File

@@ -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) {

View File

@@ -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

View File

@@ -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.

View File

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