DNET: More fixes and feedback (#2489)

* Add some re-rendering improvements to avoid the canvas and visual servers getting desynched

* removed underlevelled nerf to low-level servers; improved charisma level docs

* Remove offscreen dynamic culling

* PR feedback; add cache file names to tooltip

* Ensure stasis link servers get loaded properly; ensure darkweb has neighbors to prevent unit tests from failing; remove extra optional chaining accessors
This commit is contained in:
Michael Ficocelli
2026-02-13 20:23:24 -05:00
committed by GitHub
parent 68700ff01f
commit 26db9f2955
12 changed files with 52 additions and 62 deletions

View File

@@ -35,6 +35,7 @@ import {
} from "../Enums";
import { DarknetServerOptions, DnetServerBuilder } from "../models/DarknetServerOptions";
import {
getAllDarknetServers,
getAllMovableDarknetServers,
getNeighborsOnRow,
getServersOnRowAbove,
@@ -94,7 +95,7 @@ export const populateDarknet = () => {
return;
}
clearDarknet(true);
clearDarknet();
addLabyrinth();
addRandomDarknetServers(getNetDepth() * NET_WIDTH * SERVER_DENSITY - 10);
addRandomDarknetServers(5 - DarknetState.Network[0].length);
@@ -108,13 +109,13 @@ export const populateDarknet = () => {
}
};
export const clearDarknet = (force = false) => {
export const clearDarknet = () => {
movePlayerIfNeeded();
for (let i = 0; i < MAX_NET_DEPTH; i++) {
for (let j = 0; j < NET_WIDTH; j++) {
const server = DarknetState.Network[i]?.[j];
if (!server) continue;
deleteDarknetServer(server, force);
deleteDarknetServer(server, true);
DarknetState.Network[i][j] = null;
}
}
@@ -156,9 +157,9 @@ export const loadDarknet = () => {
return;
}
const darkNetServers = getAllMovableDarknetServers();
const darkNetServers = getAllDarknetServers();
for (const server of darkNetServers) {
if (isLabyrinthServer(server.hostname)) {
if (isLabyrinthServer(server.hostname) || server.hostname === SpecialServers.DarkWeb) {
continue;
}
disconnectServer(server, true);

View File

@@ -203,6 +203,7 @@ export const addLowLevelServersIfNeeded = (): void => {
const serversConnectedToDarkweb = getAllDarknetServers().filter((s) => s.depth === 0);
if (serversConnectedToDarkweb.length <= 3) {
addRandomDarknetServers(2, 0, true);
addLowLevelServersIfNeeded();
}
if (lowLevelServers.length / (4 * NET_WIDTH) < LOW_LEVEL_SERVER_DENSITY) {
addRandomDarknetServers(2, Math.floor(Math.random() * 4));

View File

@@ -79,10 +79,9 @@ export const calculateAuthenticationTime = (
const threadsFactor = 1 / (1 + 0.2 * (threads - 1));
const skillFactor = (diffFactor * chaRequired + baseDiff) / (person.skills.charisma + 100);
const noobFactor = Math.min(0.5 + difficulty / 4, 1);
const backdoorFactor = getBackdoorAuthTimeDebuff();
const underleveledFactor =
person.skills.charisma >= chaRequired ? 1 : 1.5 + (chaRequired + 50) / (person.skills.charisma + 50);
const applyUnderleveledFactor = person.skills.charisma <= chaRequired && darknetServerData.depth > 1;
const underleveledFactor = applyUnderleveledFactor ? 1.5 + (chaRequired + 50) / (person.skills.charisma + 50) : 1;
const hasBootsFactor = Player.hasAugmentation(AugmentationName.TheBoots) ? 0.8 : 1;
const hasSf15_2Factor = Player.activeSourceFileLvl(15) > 2 ? 0.8 : 1;
const bonusTimeFactor = hasDarknetBonusTime() ? 0.75 : 1;
@@ -90,7 +89,6 @@ export const calculateAuthenticationTime = (
const time =
baseTime *
skillFactor *
noobFactor *
backdoorFactor *
underleveledFactor *
hasBootsFactor *

View File

@@ -58,36 +58,33 @@ export function NetworkDisplayWrapper(): React.ReactElement {
[draggableBackground],
);
useEffect(() => {
const clearSubscription = DarknetEvents.subscribe(() => {
if (canvas.current) {
const lab = getLabyrinthDetails().lab;
const startingDepth = lab && getServerLogs(lab, 1, true).length ? lab.depth : 0;
const deepestServer = DarknetState.Network.flat().reduce((deepest, server) => {
if (server?.hasAdminRights && server.depth > deepest) {
return server.depth;
}
return deepest;
}, startingDepth);
const visibilityMargin = DarknetState.showFullNetwork ? 99 : 3;
setNetDisplayDepth(deepestServer + visibilityMargin);
const updateDisplay = useCallback(() => {
if (!canvas.current) {
return;
}
const visibilityMargin = DarknetState.showFullNetwork ? 99 : 3;
const lab = getLabyrinthDetails().lab;
const startingDepth = lab && getServerLogs(lab, 1, true).length ? lab.depth : 0;
const deepestServerDepth = DarknetState.Network.flat().reduce(
(deepest, server) => (server?.hasAdminRights && server.depth > deepest ? server.depth : deepest),
startingDepth,
);
setNetDisplayDepth(deepestServerDepth + visibilityMargin);
rerender();
drawOnCanvas(canvas.current);
}
});
canvas.current && drawOnCanvas(canvas.current);
rerender();
drawOnCanvas(canvas.current);
}, [rerender]);
useEffect(() => {
const clearSubscription = DarknetEvents.subscribe(() => updateDisplay());
draggableBackground.current?.addEventListener("wheel", (e) => e.preventDefault());
scrollTo(DarknetState.netViewTopScroll, DarknetState.netViewLeftScroll);
updateDisplay();
return () => {
clearSubscription();
};
}, [rerender, scrollTo]);
useEffect(() => {
DarknetEvents.emit();
}, []);
}, [updateDisplay, rerender, scrollTo]);
const allowAuth = (server: DarknetServer | null) =>
!!server &&
@@ -189,24 +186,6 @@ export function NetworkDisplayWrapper(): React.ReactElement {
throttledZoom(wheelEvent as unknown as WheelEvent);
};
const isWithinScreen = (server: DarknetServer) => {
const { left, top } = getPixelPosition(server, true);
const background = draggableBackground.current;
const buffer = 600;
const visibleAreaLeftEdge = (background?.scrollLeft ?? 0) / zoomOptions[zoomIndex];
const visibleAreaTopEdge = (background?.scrollTop ?? 0) / zoomOptions[zoomIndex];
const visibleAreaRightEdge =
visibleAreaLeftEdge + ((background?.clientWidth ?? 0) / zoomOptions[zoomIndex] ** 2 || window.innerWidth);
const visibleAreaBottomEdge =
visibleAreaTopEdge + ((background?.clientHeight ?? 0) / zoomOptions[zoomIndex] ** 2 || window.innerHeight);
return (
left >= visibleAreaLeftEdge - buffer &&
left <= visibleAreaRightEdge + buffer &&
top >= visibleAreaTopEdge - buffer &&
top <= visibleAreaBottomEdge + buffer
);
};
const search = (selection: string, options: string[], searchTerm: string) => {
if (searchTerm.length === 1) {
return;
@@ -317,12 +296,11 @@ export function NetworkDisplayWrapper(): React.ReactElement {
style={{ position: "absolute", zIndex: -1 }}
></canvas>
{darkWebRoot && <ServerStatusBox server={darkWebRoot} enableAuth={true} classes={classes} />}
{DarknetState.Network.slice(0, netDisplayDepth).map((row, i) =>
{DarknetState.Network.slice(0, netDisplayDepth).map((row) =>
row.map(
(server, j) =>
server &&
isWithinScreen(server) && (
<ServerStatusBox server={server} key={`${i},${j}`} enableAuth={allowAuth(server)} classes={classes} />
(server) =>
server && (
<ServerStatusBox server={server} key={server.ip} enableAuth={allowAuth(server)} classes={classes} />
),
),
)}

View File

@@ -48,6 +48,9 @@ export function ServerSummary({
runningScriptNames.length > 3 ? ` +${runningScriptNames.length - 3}` : ""
}`
: "No running scripts on server";
const dataCacheTooltip = `Reward caches on server: ${server.caches.slice(0, 3).join(", ")}${
server.caches.length > 3 ? ` +${server.caches.length - 3}` : ""
}`;
const hasStormSeed = server.programs.includes(CompletedProgramName.stormSeed);
const hasBackdoor = server.backdoorInstalled && !server.hasStasisLink;
const ramBlockedDetails = formatToMaxDigits(server.blockedRam, 2) + "GB";
@@ -65,7 +68,7 @@ export function ServerSummary({
const components = [];
if (cacheCount) {
components.push(
<Tooltip key="cache" title={<>Reward cache count: {cacheCount}</>}>
<Tooltip key="cache" title={<>{dataCacheTooltip}</>}>
<Typography>
<SvgIcon component={Inventory2} className={`${classes.gold} ${classes.serverStatusIcon}`} />
{cacheCount}

View File

@@ -32,8 +32,9 @@ export const drawOnCanvas = (canvas: HTMLCanvasElement) => {
for (const connectedServerName of server.serversOnNetwork) {
const connectedServer = getDarknetServerOrThrow(connectedServerName);
if (
!connectedServer.hasAdminRights &&
!connectedServer.serversOnNetwork.find((s) => getDarknetServerOrThrow(s).hasAdminRights)
!connectedServer ||
(!connectedServer.hasAdminRights &&
!connectedServer.serversOnNetwork.find((s) => getDarknetServerOrThrow(s).hasAdminRights))
) {
continue;
}

View File

@@ -191,7 +191,7 @@ export function DarknetDev(): React.ReactElement {
<Tooltip title={<Typography>Create a new darkweb network.</Typography>}>
<Button
onClick={() => {
clearDarknet(true);
clearDarknet();
populateDarknet();
SnackbarEvents.emit("New dark network generated", ToastVariant.SUCCESS, 2000);
}}

View File

@@ -4450,6 +4450,9 @@ export interface Darknet {
*
* If successful, grants the script a session, allowing it to exec() scripts on that server, or scp() files to it. (scp() *from* the server is always allowed.)
*
* Note that the charisma level on a server is not a requirement for authentication, but authentication takes longer
* if the player's charisma is below the server's charisma level.
*
* @remarks
* RAM cost: 0.6 GB
*
@@ -4489,6 +4492,7 @@ export interface Darknet {
* Servers will periodically produce logs themselves, as well, which sometimes are useful, but most times are not.
*
* The speed of capture scales with the number of threads used. See formulas.dnet.getHeartbleedTime for more information.
* Note that you cannot scrape logs from servers whose required charisma is higher than your charisma level.
*
* @remarks
* RAM cost: 0.6 GB