DNET: Add JS object properties as server names; refactor save/load/server storage to support this (#2482)

This commit is contained in:
Michael Ficocelli
2026-02-10 03:13:47 -05:00
committed by GitHub
parent b99e522d1c
commit bab6280735
14 changed files with 213 additions and 120 deletions

View File

@@ -135,8 +135,8 @@ export const clearDarknet = (force = false) => {
DarknetState.allowMutating = true;
DarknetState.openServer = null;
DarknetState.stockPromotions = {};
DarknetState.migrationInductionServers = {};
DarknetState.serverState = {};
DarknetState.migrationInductionServers = new Map();
DarknetState.serverState = new Map();
};
export const movePlayerIfNeeded = (server?: DarknetServer) => {

View File

@@ -267,16 +267,17 @@ export const chargeServerMigration = (server: DarknetServer, threads = 1) => {
const chargeIncrease = ((Player.skills.charisma + 500) / (server.difficulty * 200 + 1000)) * 0.01 * threads;
const xpGained = Player.mults.charisma_exp * 50 * ((200 + Player.skills.charisma) / 200) * threads;
Player.gainCharismaExp(xpGained);
DarknetState.migrationInductionServers[server.hostname] =
(DarknetState.migrationInductionServers[server.hostname] ?? 0) + chargeIncrease;
const currentCharge = DarknetState.migrationInductionServers.get(server.hostname) ?? 0;
const newCharge = Math.min(currentCharge + chargeIncrease, 1);
DarknetState.migrationInductionServers.set(server.hostname, newCharge);
const result = {
chargeIncrease,
newCharge: Math.min(DarknetState.migrationInductionServers[server.hostname], 1),
newCharge: newCharge,
xpGained: xpGained,
};
if (DarknetState.migrationInductionServers[server.hostname] >= 1) {
if (newCharge >= 1) {
moveDarknetServer(server, -2, 4);
DarknetState.migrationInductionServers[server.hostname] = 0;
DarknetState.migrationInductionServers.set(server.hostname, 0);
}
return result;
};

View File

@@ -44,12 +44,12 @@ export const DarknetState = {
lastStormTime: new Date(),
stockPromotions: {} as Record<string, number>,
migrationInductionServers: {} as Record<string, number>,
migrationInductionServers: new Map<string, number>(),
/**
* Do NOT access the server state directly via this property. You must call getServerState.
*/
serverState: {} as Record<string, ServerState>,
serverState: new Map<string, ServerState>(),
offlineServers: [] as string[],
showFullNetwork: false,
zoomIndex: 7,
@@ -61,14 +61,17 @@ export const DarknetState = {
* Get the server state. It will initialize the state if it does not exist in DarknetState.serverState.
*/
export const getServerState = (hostname: string): ServerState => {
if (!DarknetState.serverState[hostname]) {
DarknetState.serverState[hostname] = {
serverLogs: [],
lastLogTime: undefined,
authenticatedPIDs: [],
};
const currentState = DarknetState.serverState.get(hostname);
if (currentState) {
return currentState;
}
return DarknetState.serverState[hostname];
const newState = {
serverLogs: [],
lastLogTime: undefined,
authenticatedPIDs: [],
};
DarknetState.serverState.set(hostname, newState);
return newState;
};
/**

View File

@@ -209,6 +209,8 @@ export const presetNames = [
"Anor_Londo",
"The_Painted_World",
"The_Depths",
"__proto__",
"constructor",
] as const;
export const ServerNamePrefixes = [

View File

@@ -25,7 +25,7 @@ export class RunningScript {
// Map of [key: hostname] -> Hacking data. Used for offline progress calculations.
// Hacking data format: [MoneyStolen, NumTimesHacked, NumTimesGrown, NumTimesWeaken]
dataMap: Record<string, number[]> = {};
dataMap: Map<string, number[]> = new Map();
// Script filename
filename = "default.js" as ScriptFilePath;
@@ -129,27 +129,23 @@ export class RunningScript {
// Update the moneyStolen and numTimesHack maps when hacking
recordHack(hostname: string, moneyGained: number, n = 1): void {
if (this.dataMap[hostname] == null || this.dataMap[hostname].constructor !== Array) {
this.dataMap[hostname] = [0, 0, 0, 0];
}
this.dataMap[hostname][0] += moneyGained;
this.dataMap[hostname][1] += n;
this.initDataMapIfNeeded(hostname);
const [hackMoney, hackCount, growCount, weakenCount] = this.dataMap.get(hostname) ?? [];
this.dataMap.set(hostname, [hackMoney + moneyGained, hackCount + n, growCount, weakenCount]);
}
// Update the grow map when calling grow()
recordGrow(hostname: string, n = 1): void {
if (this.dataMap[hostname] == null || this.dataMap[hostname].constructor !== Array) {
this.dataMap[hostname] = [0, 0, 0, 0];
}
this.dataMap[hostname][2] += n;
this.initDataMapIfNeeded(hostname);
const [hackMoney, hackCount, growCount, weakenCount] = this.dataMap.get(hostname) ?? [];
this.dataMap.set(hostname, [hackMoney, hackCount, growCount + n, weakenCount]);
}
// Update the weaken map when calling weaken() {
recordWeaken(hostname: string, n = 1): void {
if (this.dataMap[hostname] == null || this.dataMap[hostname].constructor !== Array) {
this.dataMap[hostname] = [0, 0, 0, 0];
}
this.dataMap[hostname][3] += n;
this.initDataMapIfNeeded(hostname);
const [hackMoney, hackCount, growCount, weakenCount] = this.dataMap.get(hostname) ?? [];
this.dataMap.set(hostname, [hackMoney, hackCount, growCount, weakenCount + n]);
}
// Serialize the current object to a JSON save state
@@ -157,7 +153,10 @@ export class RunningScript {
// Omit the title if it's a ReactNode, it will be filled in with the default on load.
return Generic_toJSON(
"RunningScript",
this,
{
...this,
dataMap: Object.fromEntries(this.dataMap.entries()),
},
typeof this.title === "string" ? includedProperties : includedPropsNoTitle,
);
}
@@ -165,14 +164,27 @@ export class RunningScript {
// Initializes a RunningScript Object from a JSON save state
static fromJSON(value: IReviverValue): RunningScript {
const runningScript = Generic_fromJSON(RunningScript, value.data, includedProperties);
const validEntries = Object.entries(runningScript.dataMap).filter(isValidDataMapEntry);
runningScript.dataMap = new Map(validEntries);
if (!runningScript.scriptKey) runningScript.scriptKey = scriptKey(runningScript.filename, runningScript.args);
if (!runningScript.title) runningScript.title = `${runningScript.filename} ${runningScript.args.join(" ")}`;
return runningScript;
}
initDataMapIfNeeded(hostname: string) {
if (!this.dataMap.has(hostname)) {
this.dataMap.set(hostname, [0, 0, 0, 0]);
}
}
}
const includedProperties = getKeyList(RunningScript, {
removedKeys: ["logs", "dependencies", "logUpd", "pid", "parent", "tailProps"],
});
const includedPropsNoTitle = includedProperties.filter((x) => x !== "title");
function isValidDataMapEntry(entry: [string, unknown]): entry is [string, number[]] {
const [, value] = entry;
return Array.isArray(value) && value.length === 4 && value.every((v) => typeof v === "number");
}
constructorsForReviver.RunningScript = RunningScript;

View File

@@ -32,29 +32,25 @@ export function scriptCalculateOfflineProduction(
//Data map: [MoneyStolen, NumTimesHacked, NumTimesGrown, NumTimesWeaken]
// Grow
for (const hostname of Object.keys(runningScript.dataMap)) {
if (Object.hasOwn(runningScript.dataMap, hostname)) {
if (runningScript.dataMap[hostname][2] == 0 || runningScript.dataMap[hostname][2] == null) {
continue;
}
const server = GetServer(hostname);
if (server == null) {
continue;
}
const timesGrown = Math.round(
((0.5 * runningScript.dataMap[hostname][2]) / runningScript.onlineRunningTime) * timePassed,
);
runningScript.log(`Called on ${server.hostname} ${timesGrown} times while offline`);
const host = GetServer(runningScript.server);
if (host === null) {
throw new Error("getServer of null key?");
}
if (!(server instanceof Server)) {
throw new Error("trying to grow a non-normal server");
}
const growth = processSingleServerGrowth(server, timesGrown, host.cpuCores);
runningScript.log(`'${server.hostname}' grown by ${formatPercent(growth - 1, 6)} while offline`);
for (const [hostname, [, , growCount]] of runningScript.dataMap.entries()) {
if (growCount == 0 || growCount == null) {
continue;
}
const server = GetServer(hostname);
if (server == null) {
continue;
}
const timesGrown = Math.round(((0.5 * growCount) / runningScript.onlineRunningTime) * timePassed);
runningScript.log(`Called on ${server.hostname} ${timesGrown} times while offline`);
const host = GetServer(runningScript.server);
if (host === null) {
throw new Error("getServer of null key?");
}
if (!(server instanceof Server)) {
throw new Error("trying to grow a non-normal server");
}
const growth = processSingleServerGrowth(server, timesGrown, host.cpuCores);
runningScript.log(`'${server.hostname}' grown by ${formatPercent(growth - 1, 6)} while offline`);
}
// Offline EXP gain
@@ -76,26 +72,22 @@ export function scriptCalculateOfflineProduction(
runningScript.offlineMoneyMade += moneyGain;
// Weaken
for (const hostname of Object.keys(runningScript.dataMap)) {
if (Object.hasOwn(runningScript.dataMap, hostname)) {
if (runningScript.dataMap[hostname][3] == 0 || runningScript.dataMap[hostname][3] == null) {
continue;
}
const serv = GetServer(hostname);
if (serv == null) {
continue;
}
if (!(serv instanceof Server)) throw new Error("trying to weaken a non-normal server");
const host = GetServer(runningScript.server);
if (host === null) throw new Error("getServer of null key?");
const timesWeakened = Math.round(
((0.5 * runningScript.dataMap[hostname][3]) / runningScript.onlineRunningTime) * timePassed,
);
runningScript.log(`Called weaken() on ${serv.hostname} ${timesWeakened} times while offline`);
const weakenAmount = getWeakenEffect(runningScript.threads, host.cpuCores);
serv.weaken(weakenAmount * timesWeakened);
for (const [hostname, [, , , weakenCount]] of runningScript.dataMap.entries()) {
if (weakenCount == 0 || weakenCount == null) {
continue;
}
const serv = GetServer(hostname);
if (serv == null) {
continue;
}
if (!(serv instanceof Server)) throw new Error("trying to weaken a non-normal server");
const host = GetServer(runningScript.server);
if (host === null) throw new Error("getServer of null key?");
const timesWeakened = Math.round(((0.5 * weakenCount) / runningScript.onlineRunningTime) * timePassed);
runningScript.log(`Called weaken() on ${serv.hostname} ${timesWeakened} times while offline`);
const weakenAmount = getWeakenEffect(runningScript.threads, host.cpuCores);
serv.weaken(weakenAmount * timesWeakened);
}
}

View File

@@ -19,10 +19,10 @@ import { MAX_NET_DEPTH, NET_WIDTH } from "../DarkNet/Enums";
* Key (string) = IP
* Value = Server object
*/
let AllServers: Record<string, Server | HacknetServer | DarknetServer> = {};
const AllServers: Map<string, BaseServer> = new Map();
function GetServerByIP(ip: string): BaseServer | undefined {
for (const server of Object.values(AllServers)) {
for (const server of AllServers.values()) {
if (server.ip !== ip) continue;
return server;
}
@@ -30,17 +30,12 @@ function GetServerByIP(ip: string): BaseServer | undefined {
//Get server by IP or hostname. Returns null if invalid
export function GetServer(s: string): BaseServer | null {
if (Object.hasOwn(AllServers, s)) {
const server = AllServers[s];
if (server) return server;
const server = AllServers.get(s);
if (server) {
return server;
}
if (!isIPAddress(s)) return null;
const ipserver = GetServerByIP(s);
if (ipserver !== undefined) {
return ipserver;
}
return null;
return GetServerByIP(s) ?? null;
}
/**
@@ -70,24 +65,24 @@ export function GetReachableServer(s: string): BaseServer | null {
return server;
}
// Get all servers. Only includes darknet servers if showDarkweb is true.
export function GetAllServers(showDarkweb = false): BaseServer[] {
const servers: BaseServer[] = [];
for (const key of Object.keys(AllServers)) {
if (!showDarkweb && AllServers[key] instanceof DarknetServer) {
for (const server of AllServers.values()) {
if (!showDarkweb && server instanceof DarknetServer) {
continue;
}
servers.push(AllServers[key]);
servers.push(server);
}
return servers;
}
export function DeleteServer(serverkey: string): void {
for (const key of Object.keys(AllServers)) {
const server = AllServers[key];
if (server.ip !== serverkey && server.hostname !== serverkey) continue;
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
delete AllServers[key];
break;
for (const server of AllServers.values()) {
if (server.ip === serverkey || server.hostname === serverkey) {
AllServers.delete(server.hostname);
break;
}
}
}
@@ -106,8 +101,8 @@ export const disconnectServers = (server1: BaseServer, server2: BaseServer) => {
};
export function ipExists(ip: string): boolean {
for (const hostName in AllServers) {
if (AllServers[hostName].ip === ip) {
for (const server of AllServers.values()) {
if (server.ip === ip) {
return true;
}
}
@@ -141,21 +136,20 @@ export function AddToAllServers(server: Server | HacknetServer | DarknetServer):
);
}
AllServers[server.hostname] = server;
AllServers.set(server.hostname, server);
}
export const renameServer = (hostname: string, newName: string): void => {
AllServers[newName] = AllServers[hostname];
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
delete AllServers[hostname];
const existingServer = AllServers.get(hostname);
if (!existingServer) {
throw new Error(`Cannot rename server. No server found with hostname ${hostname}`);
}
AllServers.set(newName, existingServer);
AllServers.delete(hostname);
};
export function prestigeAllServers(): void {
for (const member of Object.keys(AllServers)) {
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
delete AllServers[member];
}
AllServers = {};
AllServers.clear();
// WIP: Check other properties in DarknetState as well, then improve validateDarknetNetwork.
DarknetState.Network = new Array(MAX_NET_DEPTH)
.fill(null)
@@ -168,18 +162,19 @@ export function loadAllServers(saveString: string): void {
if (Object.keys(allServersData).length === 0) {
throw new Error("Server list is empty.");
}
AllServers.clear();
for (const [serverName, server] of Object.entries(allServersData)) {
if (!(server instanceof Server) && !(server instanceof HacknetServer) && !(server instanceof DarknetServer)) {
throw new Error(`Server ${serverName} is not an instance of Server or HacknetServer.`);
throw new Error(`Server ${serverName} is not an instance of Server or HacknetServer or DarknetServer.`);
} else {
AllServers.set(serverName, server);
}
}
// We validated the data above, so it's safe to typecast here.
AllServers = allServersData as typeof AllServers;
// Apply blocked ram for darknet servers
applyRamBlocks();
}
export function saveAllServers(): string {
return JSON.stringify(AllServers);
return JSON.stringify(Object.fromEntries(AllServers.entries()));
}

View File

@@ -232,7 +232,7 @@ function LogWindow({ hidden, script, onClose }: LogWindowProps): React.ReactElem
}
// Reset some things, because we're reusing the RunningScript instance
script.ramUsage = ramUsage;
script.dataMap = {};
script.dataMap = new Map();
script.onlineExpGained = 0;
script.onlineMoneyMade = 0;
script.onlineRunningTime = 0.01;