Files
bitburner-src/src/NetscriptFunctions/Darknet.ts

735 lines
28 KiB
TypeScript

import type { InternalAPI, NetscriptContext } from "../Netscript/APIWrapper";
import type { Darknet as DarknetAPI, DarknetResult } from "@nsdefs";
import { helpers } from "../Netscript/NetscriptHelpers";
import {
calculateAuthenticationTime,
calculatePasswordAttemptChaGain,
chargeServerMigration,
getBackdoorAuthTimeDebuff,
getSetStasisLinkDuration,
getStasisLinkLimit,
setStasisLink,
} from "../DarkNet/effects/effects";
import { Player } from "@player";
import { formatNumber } from "../ui/formatNumber";
import { GetServer } from "../Server/AllServers";
import { capturePackets } from "../DarkNet/models/packetSniffing";
import { addSessionToServer, DarknetState, getServerState } from "../DarkNet/models/DarknetState";
import { getStockFromSymbol } from "./StockMarket";
import { CompletedProgramName } from "@enums";
import { handleStormSeed } from "../DarkNet/effects/webstorm";
import { getPasswordType } from "../DarkNet/controllers/ServerGenerator";
import { checkPassword, getAuthResult, isAuthenticated } from "../DarkNet/effects/authentication";
import {
getLabMaze,
getLabyrinthDetails,
getLabyrinthLocationReport,
getSurroundingsVisualized,
isLabyrinthServer,
} from "../DarkNet/effects/labyrinth";
import { getPhishingAttackSpeed, handlePhishingAttack } from "../DarkNet/effects/phishing";
import { handleRamBlockRemoved } from "../DarkNet/effects/ramblock";
import {
expectDarknetAccess,
expectRunningOnDarknetServer,
checkDarknetServer,
getTimeoutChance,
isDirectConnected,
logger,
} from "../DarkNet/effects/offlineServerHandling";
import { DarknetServer } from "../Server/DarknetServer";
import { GenericResponseMessage, ResponseCodeEnum } from "../DarkNet/Enums";
import { getRewardFromCache } from "../DarkNet/effects/cacheFiles";
import { CONSTANTS } from "../Constants";
import { getStasisLinkServers } from "../DarkNet/utils/darknetNetworkUtils";
import { resolveCacheFilePath } from "../Paths/CacheFilePath";
import type { CacheResult } from "@nsdefs";
import { MAX_PASSWORD_LENGTH } from "../DarkNet/Constants";
import { isIPAddress } from "../Types/strings";
import { getDarknetServerOrThrow } from "../DarkNet/utils/darknetServerUtils";
import { shuffle } from "lodash";
type CompleteHeartbleedOptions = {
peek: boolean;
logsToCapture: number;
additionalMsec: number;
};
function heartbleedOptions(ctx: NetscriptContext, opts: unknown): CompleteHeartbleedOptions {
const defaults = {
peek: false,
logsToCapture: 1,
additionalMsec: 0,
};
if (opts == null) {
return defaults;
}
if (typeof opts !== "object") {
throw helpers.errorMessage(ctx, `Invalid arguments: "options" is not an object`);
}
const options = {
...defaults,
...opts,
};
const peek = helpers.boolean(ctx, "options.peek", options.peek);
const logsToCapture = helpers.positiveInteger(ctx, "options.logsToCapture", options.logsToCapture);
const additionalMsec = helpers.integer(ctx, "options.additionalMsec", options.additionalMsec);
if (additionalMsec < 0) {
throw helpers.errorMessage(
ctx,
`Invalid arguments: "options.additionalMsec" (${options.additionalMsec}) must be a non-negative integer`,
);
}
return {
peek,
logsToCapture,
additionalMsec,
};
}
export function NetscriptDarknet(): InternalAPI<DarknetAPI> {
return {
authenticate:
(ctx: NetscriptContext) =>
(_host, _password, _additionalMsec): Promise<DarknetResult> => {
const targetHost = helpers.string(ctx, "host", _host);
const password = helpers.string(ctx, "password", _password);
const additionalMsec = helpers.number(ctx, "additionalMsec", _additionalMsec ?? 0);
if (additionalMsec < 0) {
throw helpers.errorMessage(ctx, `Invalid arguments: "additionalMsec" is not a positive integer`);
}
if (password.length > MAX_PASSWORD_LENGTH * 2) {
// No password will ever be this long, and this prevents extremely long password attempts from causing performance issues,
// or feedback loops where longer and longer passwords are attempted due to player script bugs.
throw helpers.errorMessage(
ctx,
`Invalid arguments: "password" is too long. Attempted length: ${
password.length
}. Attempted password starts with ${password.slice(0, 100)} `,
);
}
const serverCheck = checkDarknetServer(ctx, targetHost, {
requireDirectConnection: true,
});
if (!serverCheck.success) {
return helpers.netscriptDelay(ctx, 100).then(() => ({
success: false,
code: serverCheck.code,
message: serverCheck.message,
}));
}
const server = serverCheck.server;
const threads = ctx.workerScript.scriptRef.threads;
const networkDelay = calculateAuthenticationTime(server, Player, threads, password) + additionalMsec;
logger(ctx)(
`Connecting to ${server.hostname} with password '${password}'... (Est: ${formatNumber(
networkDelay / 1000,
1,
)}s)`,
);
return helpers.netscriptDelay(ctx, networkDelay).then(() => {
const serverCheck = checkDarknetServer(ctx, targetHost, { requireDirectConnection: true });
if (!serverCheck.success) {
return helpers.netscriptDelay(ctx, 100).then(() => ({
success: false,
code: serverCheck.code,
message: serverCheck.message,
}));
}
const server = serverCheck.server;
// Authentication has a chance to timeout based on darknet instability
if (Math.random() < getTimeoutChance()) {
logger(ctx)(`Authentication to ${server.hostname} timed out due to network instability. Please try again.`);
return {
success: false,
code: ResponseCodeEnum.RequestTimeOut,
message: GenericResponseMessage.RequestTimeOut,
};
}
const authResult = getAuthResult(server, password, threads, networkDelay, ctx.workerScript.pid);
const success = authResult.result.success;
const xp = formatNumber(calculatePasswordAttemptChaGain(server, threads, success), 1);
logger(ctx)(
`Authentication on ${server.hostname} ${success ? "succeeded" : `failed. (Gained ${xp} cha xp)`}`,
);
if (isLabyrinthServer(server.hostname)) {
return {
success: success,
code: success ? ResponseCodeEnum.Success : ResponseCodeEnum.AuthFailure,
message: authResult.response.message,
data: authResult.response.data,
};
}
return {
success: success,
code: success ? ResponseCodeEnum.Success : ResponseCodeEnum.AuthFailure,
message: success ? GenericResponseMessage.Success : GenericResponseMessage.AuthFailure,
};
});
},
connectToSession:
(ctx: NetscriptContext) =>
(_host, _password): DarknetResult => {
const targetHost = helpers.string(ctx, "host", _host);
const token = helpers.string(ctx, "password", _password);
if (token.length > 100) {
throw helpers.errorMessage(
ctx,
`Invalid arguments: "password" is too long. Attempted length: ${
token.length
}. Attempted password starts with ${token.slice(0, 100)} `,
);
}
const serverCheck = checkDarknetServer(ctx, targetHost, {
requireAdminRights: true,
});
if (!serverCheck.success) {
return {
success: false,
code: serverCheck.code,
message: serverCheck.message,
};
}
const server = serverCheck.server;
const result = checkPassword(server, token, ctx.workerScript.scriptRef.threads, ctx.workerScript.pid);
if (result.code !== ResponseCodeEnum.Success) {
logger(ctx)(
`${server.hostname} does not recognise that password. Use ns.dnet.authenticate() to create a session.`,
);
return {
success: false,
code: ResponseCodeEnum.AuthFailure,
message: GenericResponseMessage.AuthFailure,
};
}
addSessionToServer(server, ctx.workerScript.pid);
logger(ctx)(`Authentication on ${server.hostname} succeeded.`);
return {
success: true,
code: ResponseCodeEnum.Success,
message: GenericResponseMessage.Success,
};
},
heartbleed:
(ctx: NetscriptContext) =>
(_host, _opts): Promise<DarknetResult & { logs: string[] }> => {
const targetHost = helpers.string(ctx, "host", _host ?? ctx.workerScript.hostname);
const options = heartbleedOptions(ctx, _opts);
const serverCheck = checkDarknetServer(ctx, targetHost, {
requireDirectConnection: true,
});
if (!serverCheck.success) {
return helpers.netscriptDelay(ctx, 100).then(() => ({
success: false,
code: serverCheck.code,
message: serverCheck.message,
logs: [],
}));
}
const server = serverCheck.server;
const networkDelay =
calculateAuthenticationTime(server, Player, ctx.workerScript.scriptRef.threads) * 1.5 +
(options.additionalMsec ?? 0);
logger(ctx)(
`Attempting to extract data from ${server.hostname}... (Est: ${formatNumber(networkDelay / 1000, 1)}s)`,
);
DarknetState.hasUsedHeartbleed = true;
if (Player.skills.charisma < server.requiredCharismaSkill) {
logger(ctx)(
`You need a higher charisma level to extract data from ${server.hostname}. (${server.requiredHackingSkill} required)`,
);
return helpers.netscriptDelay(ctx, 100).then(() => ({
success: false,
code: ResponseCodeEnum.NotEnoughCharisma,
message: GenericResponseMessage.NotEnoughCharisma,
logs: [],
}));
}
return helpers.netscriptDelay(ctx, networkDelay).then(() => {
const xpGained = Player.mults.charisma_exp * 50 * ((500 + Player.skills.charisma) / 500);
Player.gainCharismaExp(xpGained);
const serverCheck = checkDarknetServer(ctx, targetHost, { requireDirectConnection: true });
if (!serverCheck.success) {
return {
success: false,
code: serverCheck.code,
message: serverCheck.message,
logs: [],
};
}
const serverState = getServerState(server.hostname);
logger(ctx)(`Extracted log data from ${server.hostname}... (Gained ${formatNumber(xpGained, 1)} cha xp)`);
const capturedLogs = serverState.serverLogs.slice(0, options.logsToCapture);
if (!options.peek) {
serverState.serverLogs = serverState.serverLogs.slice(options.logsToCapture);
}
return {
success: true,
code: ResponseCodeEnum.Success,
message: GenericResponseMessage.Success,
logs: capturedLogs.map((log) =>
typeof log.message === "string" ? log.message : JSON.stringify(log.message),
),
};
});
},
openCache:
(ctx: NetscriptContext) =>
(_fileName, _suppressToast): CacheResult => {
const fileName = helpers.string(ctx, "fileName", _fileName);
const suppressToast = helpers.boolean(ctx, "suppressToast", _suppressToast ?? false);
const server = expectRunningOnDarknetServer(ctx);
expectDarknetAccess(ctx);
const path = resolveCacheFilePath(fileName);
if (!path) {
throw helpers.errorMessage(ctx, `Invalid cache file. (File must end in .cache) : ${fileName}`);
}
const hasCacheFile = server.caches.includes(path);
if (!hasCacheFile) {
throw helpers.errorMessage(ctx, `Cache file not found: ${fileName} on server ${server.hostname}`);
}
server.caches = server.caches.filter((cache) => cache !== fileName);
const result = getRewardFromCache(server, fileName, suppressToast);
logger(ctx)(`Data file ${fileName} opened. ${result.message}.`);
return result;
},
probe:
(ctx: NetscriptContext) =>
(_returnByIp): string[] => {
const returnByIP = helpers.boolean(ctx, "returnByIP", _returnByIp ?? false);
const server = ctx.workerScript.getServer();
const out = [];
for (const neighbor of server.serversOnNetwork) {
const neighborServer = GetServer(neighbor);
if (!(neighborServer instanceof DarknetServer)) {
continue;
}
const entry = helpers.returnServerID(neighborServer, { returnByIP });
if (entry) {
out.push(entry);
}
}
helpers.log(ctx, () => `Returned ${out.length} connections for ${server.hostname}`);
// The order of results is shuffled. This is to avoid clues to the network structure
// like there are in the standard network's scan results order.
return shuffle(out);
},
setStasisLink:
(ctx: NetscriptContext) =>
(_shouldLink): Promise<DarknetResult> => {
const shouldLink = helpers.boolean(ctx, "shouldLink", _shouldLink ?? true);
const targetHost = ctx.workerScript.getServer().hostname;
const serverCheck = checkDarknetServer(ctx, targetHost);
if (!serverCheck.success) {
return helpers.netscriptDelay(ctx, 100).then(() => ({
success: false,
code: serverCheck.code,
message: serverCheck.message,
}));
}
const server = serverCheck.server;
const stasisLinkCount = getStasisLinkServers().length;
const stasisLinkLimit = getStasisLinkLimit();
if (shouldLink && stasisLinkCount >= stasisLinkLimit) {
helpers.log(ctx, () => `Stasis link limit reached. (${stasisLinkCount}/${stasisLinkLimit})`);
return helpers.netscriptDelay(ctx, 100).then(() => ({
success: false,
code: ResponseCodeEnum.StasisLinkLimitReached,
message: GenericResponseMessage.StasisLinkLimitReached,
}));
}
helpers.log(
ctx,
() => `Beginning stasis ${shouldLink ? "" : "removal "}procedure on ${server.hostname}... (Est: 30s)`,
);
// setStasisLink's delay is hardcoded at 30s. We should skip this delay in Jest tests.
return helpers
.netscriptDelay(ctx, getSetStasisLinkDuration())
.then(() => setStasisLink(ctx, server, shouldLink));
},
getStasisLinkLimit: (ctx: NetscriptContext) => (): number => {
const limit = getStasisLinkLimit();
logger(ctx)(`Stasis link limit: ${limit}`);
return limit;
},
getStasisLinkedServers:
(ctx: NetscriptContext) =>
(_returnByIP): string[] => {
const returnByIp = helpers.boolean(ctx, "returnByIP", _returnByIP ?? false);
const servers = getStasisLinkServers();
const serverNames = servers.map((s) => (returnByIp ? s.ip : s.hostname));
logger(ctx)(`Stasis linked servers: ${serverNames}`);
return serverNames;
},
getServerAuthDetails: (ctx) => (_host) => {
const targetHost = helpers.string(ctx, "host", _host ?? ctx.workerScript.hostname);
const serverCheck = checkDarknetServer(ctx, targetHost);
if (!serverCheck.success) {
logger(ctx)(serverCheck.message);
return {
isOnline: false,
isConnectedToCurrentServer: false,
hasSession: false,
modelId: "",
passwordHint: "",
data: "",
logTrafficInterval: -1,
passwordLength: -1,
passwordFormat: "numeric",
} satisfies ReturnType<DarknetAPI["getServerAuthDetails"]>;
}
const targetServer = serverCheck.server;
const localServer = ctx.workerScript.getServer();
const isConnected = isDirectConnected(localServer, targetServer);
const hasSession = isAuthenticated(targetServer, ctx.workerScript.pid);
return {
isOnline: true,
isConnectedToCurrentServer: isConnected,
hasSession,
modelId: targetServer.modelId,
passwordHint: targetServer.staticPasswordHint,
data: targetServer.passwordHintData ?? "",
logTrafficInterval: targetServer.logTrafficInterval,
passwordLength: targetServer.password.length,
passwordFormat: getPasswordType(targetServer.password),
} satisfies ReturnType<DarknetAPI["getServerAuthDetails"]>;
},
packetCapture: (ctx) => (_host) => {
const targetHost = helpers.string(ctx, "host", _host ?? ctx.workerScript.hostname);
const serverCheck = checkDarknetServer(ctx, targetHost, {
requireDirectConnection: true,
});
if (!serverCheck.success) {
return helpers.netscriptDelay(ctx, 100).then(() => ({
success: false,
code: serverCheck.code,
message: serverCheck.message,
data: "",
}));
}
const server = serverCheck.server;
const networkDelay = calculateAuthenticationTime(server, Player, ctx.workerScript.scriptRef.threads) * 4;
const xp = formatNumber(calculatePasswordAttemptChaGain(server, ctx.workerScript.scriptRef.threads), 1);
logger(ctx)(`Captured some outgoing transmissions from ${server.hostname}. (Gained ${xp} cha xp)`);
return helpers.netscriptDelay(ctx, networkDelay).then(() => {
return {
success: true,
code: ResponseCodeEnum.Success,
message: GenericResponseMessage.Success,
data: capturePackets(server),
};
});
},
induceServerMigration:
(ctx) =>
(_host): Promise<DarknetResult> => {
const targetHost = helpers.string(ctx, "host", _host);
const serverCheck = checkDarknetServer(ctx, targetHost, {
requireDirectConnection: true,
preventUseOnStationaryServers: true,
});
if (!serverCheck.success) {
return helpers.netscriptDelay(ctx, 100).then(() => ({
success: false,
code: serverCheck.code,
message: serverCheck.message,
}));
}
const hostOfCurrentServer = !isIPAddress(targetHost)
? ctx.workerScript.hostname
: getDarknetServerOrThrow(ctx.workerScript.hostname).ip;
if (targetHost === hostOfCurrentServer) {
const message = `Cannot induce migration on a script's own server. induceServerMigration must target a neighboring connected server.`;
logger(ctx)(message);
return helpers.netscriptDelay(ctx, 100).then(() => ({
success: false,
code: ResponseCodeEnum.DirectConnectionRequired,
message: message,
}));
}
const server = serverCheck.server;
logger(ctx)(`Inducing server migration of ${server.hostname}... (Est: 6s)`);
// induceServerMigration's delay is hardcoded at 6s. We should skip this delay in Jest tests.
return helpers.netscriptDelay(ctx, !CONSTANTS.isInTestEnvironment ? 6000 : 0).then(() => {
const serverCheck = checkDarknetServer(ctx, targetHost, {
requireDirectConnection: true,
preventUseOnStationaryServers: true,
});
if (!serverCheck.success) {
return helpers.netscriptDelay(ctx, 100).then(() => ({
success: false,
code: serverCheck.code,
message: serverCheck.message,
}));
}
const server = serverCheck.server;
const currentDepth = server.depth;
const result = chargeServerMigration(server, ctx.workerScript.scriptRef.threads);
logger(ctx)(
`Induced ${formatNumber(result.chargeIncrease * 100)}%. Migration prep is now at ${formatNumber(
result.newCharge * 100,
)}%. (Gained ${formatNumber(result.xpGained)} cha xp)`,
);
if (result.newCharge >= 1 && currentDepth < server.depth) {
logger(ctx)(`${server.hostname} has been migrated!`);
}
return {
success: true,
code: ResponseCodeEnum.Success,
message: GenericResponseMessage.Success,
};
});
},
unleashStormSeed: (ctx) => (): DarknetResult => {
expectDarknetAccess(ctx);
const server = ctx.workerScript.getServer();
const hasStormSeed = server.programs.includes(CompletedProgramName.stormSeed);
if (!hasStormSeed) {
const result = `${CompletedProgramName.stormSeed} not found on ${server.hostname}`;
logger(ctx)(result);
return {
success: false,
code: ResponseCodeEnum.NotFound,
message: GenericResponseMessage.NotFound,
};
}
const result = `The webstorm has been unleashed...`;
logger(ctx)(result);
handleStormSeed(server);
return {
success: true,
code: ResponseCodeEnum.Success,
message: GenericResponseMessage.Success,
};
},
isDarknetServer: (ctx) => (_host) => {
const targetHost = helpers.string(ctx, "host", _host ?? ctx.workerScript.hostname);
const server = GetServer(targetHost);
if (!server) {
return false;
}
if (!(server instanceof DarknetServer)) {
return false;
}
return true;
},
memoryReallocation:
(ctx) =>
(_host): Promise<DarknetResult> => {
const targetHost = helpers.string(ctx, "host", _host ?? ctx.workerScript.hostname);
const serverCheck = checkDarknetServer(ctx, targetHost, {
requireDirectConnection: true,
requireAdminRights: true,
});
if (!serverCheck.success) {
return helpers.netscriptDelay(ctx, 100).then(() => ({
success: false,
code: serverCheck.code,
message: serverCheck.message,
}));
}
const server = serverCheck.server;
if (server.blockedRam <= 0) {
logger(ctx)(`Server ${server.hostname} has no host-owned ram left to reallocate.`);
return helpers.netscriptDelay(ctx, 100).then(() => ({
success: false,
code: ResponseCodeEnum.NoBlockRAM,
message: GenericResponseMessage.NoBlockRAM,
}));
}
logger(ctx)(`Attempting to liberate RAM from '${server.hostname}'s owner ...`);
const delayTime = Math.max(8000 * (500 / (500 + Player.skills.charisma)), 200);
return helpers.netscriptDelay(ctx, delayTime).then(() => {
const serverCheck = checkDarknetServer(ctx, targetHost, {
requireDirectConnection: true,
requireAdminRights: true,
});
if (!serverCheck.success) {
return helpers.netscriptDelay(ctx, 100).then(() => ({
success: false,
code: serverCheck.code,
message: serverCheck.message,
}));
}
const server = serverCheck.server;
if (server.blockedRam <= 0) {
logger(ctx)(`Server ${server.hostname} has no host-owned ram left to reallocate.`);
return {
success: false,
code: ResponseCodeEnum.NoBlockRAM,
message: GenericResponseMessage.NoBlockRAM,
};
}
return handleRamBlockRemoved(ctx, server);
});
},
getBlockedRam:
(ctx) =>
(_host): number => {
const targetHost = helpers.string(ctx, "host", _host ?? ctx.workerScript.hostname);
const serverCheck = checkDarknetServer(ctx, targetHost);
if (!serverCheck.success) {
return 0;
}
return serverCheck.server.blockedRam;
},
getDepth:
(ctx) =>
(_host): number => {
const targetHost = helpers.string(ctx, "host", _host ?? ctx.workerScript.hostname);
const serverCheck = checkDarknetServer(ctx, targetHost);
if (!serverCheck.success) {
return -1;
}
return serverCheck.server.depth;
},
promoteStock:
(ctx: NetscriptContext) =>
(_symbol): Promise<DarknetResult> => {
const symbol = helpers.string(ctx, "symbol", _symbol);
const stock = getStockFromSymbol(ctx, symbol);
expectRunningOnDarknetServer(ctx);
expectDarknetAccess(ctx);
const waitTime = Math.max(8000 * (600 / (600 + Player.skills.charisma)), 200);
logger(ctx)(
`Spreading ${stock.name} stock propaganda to raise volatility... (Est: ${formatNumber(waitTime / 1000, 1)}s)`,
);
return helpers.netscriptDelay(ctx, waitTime).then(() => {
const threads = ctx.workerScript.scriptRef.threads;
const promotionAmount = threads * ((500 + Player.skills.charisma) / 500);
DarknetState.stockPromotions[symbol] = (DarknetState.stockPromotions[symbol] ?? 0) + promotionAmount;
const chaXp = Player.mults.charisma_exp * threads * 10 * ((200 + Player.skills.charisma) / 200);
Player.gainCharismaExp(chaXp);
logger(ctx)(`Spread promotion for ${stock.name}. (Gained ${formatNumber(chaXp, 1)} cha xp)`);
return {
success: true,
code: ResponseCodeEnum.Success,
message: GenericResponseMessage.Success,
};
});
},
phishingAttack: (ctx: NetscriptContext) => (): Promise<DarknetResult> => {
const waitTime = getPhishingAttackSpeed();
const server = expectRunningOnDarknetServer(ctx);
expectDarknetAccess(ctx);
return helpers.netscriptDelay(ctx, waitTime).then(() => {
return handlePhishingAttack(ctx, server);
});
},
getDarknetInstability: (ctx) => () => {
expectDarknetAccess(ctx);
return {
authenticationDurationMultiplier: getBackdoorAuthTimeDebuff(),
authenticationTimeoutChance: getTimeoutChance(),
};
},
nextMutation: (ctx) => () => {
expectDarknetAccess(ctx);
return DarknetState.nextMutation;
},
getServerRequiredCharismaLevel:
(ctx) =>
(_host): number => {
const targetHost = helpers.string(ctx, "host", _host);
const serverCheck = checkDarknetServer(ctx, targetHost);
if (!serverCheck.success) {
return -1;
}
return serverCheck.server.requiredCharismaSkill;
},
labreport: (ctx) => async () => {
expectDarknetAccess(ctx);
expectRunningOnDarknetServer(ctx);
const lab = getLabyrinthDetails().lab;
if (!lab) {
const status = "You feel lost...";
logger(ctx)(status);
return {
success: false,
message: status,
};
}
const currentServer = getDarknetServerOrThrow(ctx.workerScript.hostname);
if (!isDirectConnected(currentServer, lab)) {
const status = "You feel disconnected...";
logger(ctx)(status);
return {
success: false,
message: status,
};
}
const pid = ctx.workerScript.pid;
const authenticationTime = calculateAuthenticationTime(lab, Player, ctx.workerScript.scriptRef.threads);
await helpers.netscriptDelay(ctx, authenticationTime);
return getLabyrinthLocationReport(pid);
},
labradar: (ctx) => async () => {
expectDarknetAccess(ctx);
expectRunningOnDarknetServer(ctx);
const lab = getLabyrinthDetails().lab;
if (!lab) {
const status = "You feel blind...";
logger(ctx)(status);
return {
success: false,
message: status,
};
}
const currentServer = getDarknetServerOrThrow(ctx.workerScript.hostname);
if (!isDirectConnected(currentServer, lab)) {
const status = "You feel disconnected...";
logger(ctx)(status);
return {
success: false,
message: status,
};
}
const pid = ctx.workerScript.pid;
const authenticationTime = calculateAuthenticationTime(lab, Player, ctx.workerScript.scriptRef.threads);
await helpers.netscriptDelay(ctx, authenticationTime);
const [x, y] = DarknetState.labLocations[pid] ?? [1, 1];
return {
success: true,
message: getSurroundingsVisualized(getLabMaze(), x, y, 3, true, true),
};
},
};
}