diff --git a/package-lock.json b/package-lock.json index 1240704df..1d7651f5f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "bitburner", - "version": "2.2.0", + "version": "2.2.2dev", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "bitburner", - "version": "2.2.0", + "version": "2.2.2dev", "hasInstallScript": true, "license": "SEE LICENSE IN license.txt", "dependencies": { diff --git a/package.json b/package.json index c335a5325..daced8378 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "bitburner", "license": "SEE LICENSE IN license.txt", - "version": "2.2.0", + "version": "2.2.2dev", "main": "electron-main.js", "author": { "name": "Daniel Xie, Olivier Gagnon, et al." diff --git a/src/Netscript/APIWrapper.ts b/src/Netscript/APIWrapper.ts index ac0e5d78c..124ee2c15 100644 --- a/src/Netscript/APIWrapper.ts +++ b/src/Netscript/APIWrapper.ts @@ -3,18 +3,14 @@ import type { WorkerScript } from "./WorkerScript"; import { helpers } from "./NetscriptHelpers"; /** Permissive type for the documented API functions */ -type APIFn = (...args: any[]) => void; -/** Type for the actual wrapped function given to the player */ -type WrappedFn = (...args: unknown[]) => unknown; +type APIFn = (...args: any[]) => unknown; /** Type for internal, unwrapped ctx function that produces an APIFunction */ type InternalFn = (ctx: NetscriptContext) => ((...args: unknown[]) => ReturnType) & F; +/** Type constraint for an API layer. They must all fit this "shape". */ +type GenericAPI = { [key: string]: APIFn | GenericAPI }; // args, enums, and pid are excluded from the API for typing purposes via the definition of NSFull. // They do in fact exist on the external API (but are absent on the internal API and ramcost tree) -export type ExternalAPI = { - [key in keyof API]: API[key] extends APIFn ? WrappedFn : ExternalAPI; -}; - export type InternalAPI = { [key in keyof API]: API[key] extends APIFn ? InternalFn : InternalAPI; }; @@ -25,70 +21,91 @@ export type NetscriptContext = { functionPath: string; }; -export function NSProxy( +class NSProxyHandler { + ns: API; + ws: WorkerScript; + tree: string[]; + additionalData: Record; + memoed: API = {} as API; + + constructor(ws: WorkerScript, ns: API, tree: string[], additionalData: Record) { + this.ns = ns; + this.ws = ws; + this.tree = tree; + this.additionalData = additionalData; + Object.assign(this.memoed, additionalData); + } + + has(__target: unknown, key: string): boolean { + return Reflect.has(this.ns, key) || Reflect.has(this.additionalData, key); + } + + ownKeys(__target: unknown): (string | symbol)[] { + return [...Reflect.ownKeys(this.ns), ...Reflect.ownKeys(this.additionalData)]; + } + + getOwnPropertyDescriptor(__target: unknown, key: keyof API & string): PropertyDescriptor | undefined { + if (!this.has(__target, key)) return undefined; + return { value: this.get(__target, key, this), configurable: true, enumerable: true, writable: false }; + } + + defineProperty(__target: unknown, __key: unknown, __attrs: unknown): boolean { + throw new TypeError("ns instances are not modifiable!"); + } + + set(__target: unknown, __key: unknown, __attrs: unknown): boolean { + // Redundant with defineProperty, but we'll be explicit + throw new TypeError("ns instances are not modifiable!"); + } + + get(__target: unknown, key: keyof API & string, __receiver: any) { + const ours = this.memoed[key]; + if (ours) return ours; + + const field = this.ns[key]; + if (!field) return field; + + if (typeof field === "function") { + const arrayPath = [...this.tree, key]; + const functionPath = arrayPath.join("."); + const ctx = { workerScript: this.ws, function: key, functionPath }; + // Only do the context-binding once, instead of each time the function + // is called. + const func: any = field(ctx); + const wrappedFunction = function (...args: unknown[]): unknown { + // What remains *must* be called every time. + helpers.checkEnvFlags(ctx); + helpers.updateDynamicRam(ctx, getRamCost(...arrayPath)); + return func(...args); + }; + return ((this.memoed[key] as APIFn) = wrappedFunction); + } + if (typeof field === "object") { + return ((this.memoed[key] as GenericAPI) = NSProxy(this.ws, field as InternalAPI, [ + ...this.tree, + key, + ])); + } + console.warn(`Unexpected data while wrapping API.`, "tree:", this.tree, "key:", key, "field:", field); + throw new Error("Error while wrapping netscript API. See console."); + } +} + +export function NSProxy( ws: WorkerScript, ns: InternalAPI, tree: string[], - additionalData?: Record, -): ExternalAPI { - const memoed: ExternalAPI = Object.assign({} as ExternalAPI, additionalData ?? {}); - - const handler = { - has(__target: unknown, key: string) { - return Reflect.has(ns, key); - }, - ownKeys(__target: unknown) { - return Reflect.ownKeys(ns); - }, - getOwnPropertyDescriptor(__target: unknown, key: keyof API & string) { - if (!Reflect.has(ns, key)) return undefined; - return { value: this.get(__target, key, this), configurable: true, enumerable: true, writable: false }; - }, - defineProperty(__target: unknown, __key: unknown, __attrs: unknown) { - throw new TypeError("ns instances are not modifiable!"); - }, - set(__target: unknown, __key: unknown, __attrs: unknown) { - throw new TypeError("ns instances are not modifiable!"); - }, - get(__target: unknown, key: keyof API & string, __receiver: any) { - const ours = memoed[key]; - if (ours) return ours; - - const field = ns[key]; - if (!field) return field; - - if (typeof field === "function") { - const arrayPath = [...tree, key]; - const functionPath = arrayPath.join("."); - const wrappedFunction = function (...args: unknown[]): unknown { - const ctx = { workerScript: ws, function: key, functionPath }; - const func = field(ctx); //Allows throwing before ram check, for removedFunction - helpers.checkEnvFlags(ctx); - helpers.updateDynamicRam(ctx, getRamCost(...tree, key)); - return func(...args); - }; - return ((memoed[key] as WrappedFn) = wrappedFunction); - } - if (typeof field === "object") { - // TODO unplanned: Make this work generically - return ((memoed[key] as ExternalAPI) = NSProxy(ws, field as InternalAPI, [ - ...tree, - key, - ])); - } - console.warn(`Unexpected data while wrapping API.`, "tree:", tree, "key:", key, "field:", field); - throw new Error("Error while wrapping netscript API. See console."); - }, - }; - + additionalData: Record = {}, +): API { + const handler = new NSProxyHandler(ws, ns, tree, additionalData); // We target an empty Object, so that unproxied methods don't do anything. // We *can't* freeze the target, because it would break invariants on ownKeys. - return new Proxy({}, handler) as ExternalAPI; + return new Proxy({}, handler) as API; } /** Specify when a function was removed from the game, and its replacement function. */ export function removedFunction(version: string, replacement: string, replaceMsg?: boolean) { - return (ctx: NetscriptContext) => { + return (ctx: NetscriptContext) => () => { throw helpers.makeRuntimeErrorMsg( ctx, `Function removed in ${version}. ${replaceMsg ? replacement : `Please use ${replacement} instead.`}`, diff --git a/src/Netscript/Environment.ts b/src/Netscript/Environment.ts index 2a2443d73..a33f1aed6 100644 --- a/src/Netscript/Environment.ts +++ b/src/Netscript/Environment.ts @@ -1,5 +1,4 @@ import { NSFull } from "../NetscriptFunctions"; -import { ExternalAPI } from "./APIWrapper"; /** * The environment in which a script runs. The environment holds @@ -14,5 +13,5 @@ export class Environment { runningFn = ""; /** Environment variables (currently only Netscript functions) */ - vars: ExternalAPI | null = null; + vars: NSFull | null = null; } diff --git a/src/Netscript/WorkerScript.ts b/src/Netscript/WorkerScript.ts index ac5170500..5687e0ced 100644 --- a/src/Netscript/WorkerScript.ts +++ b/src/Netscript/WorkerScript.ts @@ -16,7 +16,6 @@ import { GetServer } from "../Server/AllServers"; import { BaseServer } from "../Server/BaseServer"; import { ScriptDeath } from "./ScriptDeath"; import { ScriptArg } from "./ScriptArg"; -import { ExternalAPI } from "./APIWrapper"; import { NSFull } from "../NetscriptFunctions"; export class WorkerScript { @@ -84,11 +83,7 @@ export class WorkerScript { /** Function called when the script ends. */ atExit?: () => void; - constructor( - runningScriptObj: RunningScript, - pid: number, - nsFuncsGenerator?: (ws: WorkerScript) => ExternalAPI, - ) { + constructor(runningScriptObj: RunningScript, pid: number, nsFuncsGenerator?: (ws: WorkerScript) => NSFull) { this.name = runningScriptObj.filename; this.hostname = runningScriptObj.server; diff --git a/src/NetscriptFunctions.ts b/src/NetscriptFunctions.ts index 0ac042127..02f9ee521 100644 --- a/src/NetscriptFunctions.ts +++ b/src/NetscriptFunctions.ts @@ -72,7 +72,7 @@ import { Flags } from "./NetscriptFunctions/Flags"; import { calculateIntelligenceBonus } from "./PersonObjects/formulas/intelligence"; import { CalculateShareMult, StartSharing } from "./NetworkShare/Share"; import { recentScripts } from "./Netscript/RecentScripts"; -import { ExternalAPI, InternalAPI, removedFunction, NSProxy } from "./Netscript/APIWrapper"; +import { InternalAPI, removedFunction, NSProxy } from "./Netscript/APIWrapper"; import { INetscriptExtra } from "./NetscriptFunctions/Extra"; import { ScriptDeath } from "./Netscript/ScriptDeath"; import { getBitNodeMultipliers } from "./BitNode/BitNode"; @@ -1894,7 +1894,7 @@ Object.assign(ns, { getServerRam: removedFunction("v2.2.0", "getServerMaxRam and getServerUsedRam"), }); -export function NetscriptFunctions(ws: WorkerScript): ExternalAPI { +export function NetscriptFunctions(ws: WorkerScript): NSFull { return NSProxy(ws, ns, [], { args: ws.args.slice(), pid: ws.pid, enums }); } diff --git a/src/NetscriptFunctions/Extra.ts b/src/NetscriptFunctions/Extra.ts index 689041c47..ccc0ab1fb 100644 --- a/src/NetscriptFunctions/Extra.ts +++ b/src/NetscriptFunctions/Extra.ts @@ -7,7 +7,7 @@ import { InternalAPI } from "../Netscript/APIWrapper"; import { helpers } from "../Netscript/NetscriptHelpers"; import { Terminal } from "../Terminal"; -export interface INetscriptExtra { +export type INetscriptExtra = { heart: { break(): number; }; @@ -18,7 +18,7 @@ export interface INetscriptExtra { rainbow(guess: string): void; iKnowWhatImDoing(): void; printRaw(value: React.ReactNode): void; -} +}; export function NetscriptExtra(): InternalAPI { return { diff --git a/src/NetscriptWorker.ts b/src/NetscriptWorker.ts index 0b2e49ab8..aaa38fe94 100644 --- a/src/NetscriptWorker.ts +++ b/src/NetscriptWorker.ts @@ -83,11 +83,6 @@ async function startNetscript1Script(workerScript: WorkerScript): Promise type BasicObject = Record; const wrappedNS = NetscriptFunctions(workerScript); function wrapNS1Layer(int: Interpreter, intLayer: unknown, nsLayer = wrappedNS as BasicObject) { - if (nsLayer === wrappedNS) { - int.setProperty(intLayer, "args", int.nativeToPseudo(nsLayer.args)); - int.setProperty(intLayer, "enums", int.nativeToPseudo(nsLayer.enums)); - int.setProperty(intLayer, "pid", nsLayer.pid); - } for (const [name, entry] of Object.entries(nsLayer)) { if (typeof entry === "function") { const wrapper = async (...args: unknown[]) => { diff --git a/src/Script/ScriptModule.ts b/src/Script/ScriptModule.ts index f10d9b06c..ac9529e93 100644 --- a/src/Script/ScriptModule.ts +++ b/src/Script/ScriptModule.ts @@ -1,8 +1,7 @@ import { NSFull } from "../NetscriptFunctions"; -import { ExternalAPI } from "../Netscript/APIWrapper"; import { AutocompleteData } from "@nsdefs"; export interface ScriptModule { - main?: (ns: ExternalAPI) => unknown; + main?: (ns: NSFull) => unknown; autocomplete?: (data: AutocompleteData, flags: string[]) => unknown; } diff --git a/src/ScriptEditor/NetscriptDefinitions.d.ts b/src/ScriptEditor/NetscriptDefinitions.d.ts index 2f4e81aa0..072dc77fa 100644 --- a/src/ScriptEditor/NetscriptDefinitions.d.ts +++ b/src/ScriptEditor/NetscriptDefinitions.d.ts @@ -1000,7 +1000,7 @@ interface NetscriptPort { * Stock market API * @public */ -export interface TIX { +export type TIX = { /** * Returns true if the player has access to a WSE Account * @remarks RAM cost: 0.05 GB @@ -1439,7 +1439,7 @@ export interface TIX { * @returns True if you successfully purchased it or if you already have access, false otherwise. */ purchaseTixApi(): boolean; -} +}; /** * Singularity API @@ -1448,7 +1448,7 @@ export interface TIX { * Source-File 4 levels. * @public */ -export interface Singularity { +export type Singularity = { /** * Backup game save. * @remarks @@ -2292,7 +2292,7 @@ export interface Singularity { * @returns - An object representing the current work. Fields depend on the kind of work. */ getCurrentWork(): any | null; -} +}; /** * Hacknet API @@ -2300,7 +2300,7 @@ export interface Singularity { * Not all these functions are immediately available. * @public */ -export interface Hacknet { +export type Hacknet = { /** * Get the number of hacknet nodes you own. * @remarks @@ -2651,7 +2651,7 @@ export interface Hacknet { * @returns Multiplier. */ getTrainingMult(): number; -} +}; /** * Bladeburner API @@ -2660,7 +2660,7 @@ export interface Hacknet { * or have Source-File 7 in order to use this API. * @public */ -export interface Bladeburner { +export type Bladeburner = { /** * List all contracts. * @remarks @@ -3148,13 +3148,13 @@ export interface Bladeburner { * * @returns whether player is a member of bladeburner division. */ inBladeburner(): boolean; -} +}; /** * Coding Contract API * @public */ -export interface CodingContract { +export type CodingContract = { /** * Attempts a coding contract, returning a reward string on success or empty string on failure. * @remarks @@ -3259,7 +3259,7 @@ export interface CodingContract { * RAM cost: 2 GB */ getContractTypes(): string[]; -} +}; /** * Gang API @@ -3267,7 +3267,7 @@ export interface CodingContract { * If you are not in BitNode-2, then you must have Source-File 2 in order to use this API. * @public */ -export interface Gang { +export type Gang = { /** * Create a gang. * @remarks @@ -3526,7 +3526,7 @@ export interface Gang { * @returns Bonus time for the Gang mechanic in milliseconds. */ getBonusTime(): number; -} +}; /** * Sleeve API @@ -3534,7 +3534,7 @@ export interface Gang { * If you are not in BitNode-10, then you must have Source-File 10 in order to use this API. * @public */ -export interface Sleeve { +export type Sleeve = { /** * Get the number of sleeves you own. * @remarks @@ -3764,7 +3764,7 @@ export interface Sleeve { * @returns True if the sleeve started working out, false otherwise. */ setToBladeburnerAction(sleeveNumber: number, action: string, contract?: string): boolean; -} +}; /** * Grafting API @@ -3772,7 +3772,7 @@ export interface Sleeve { * This API requires Source-File 10 to use. * @public */ -export interface Grafting { +export type Grafting = { /** * Retrieve the grafting cost of an aug. * @remarks @@ -3819,13 +3819,13 @@ export interface Grafting { * @throws Will error if called while you are not in New Tokyo. */ graftAugmentation(augName: string, focus?: boolean): boolean; -} +}; /** * Skills formulas * @public */ -interface SkillsFormulas { +type SkillsFormulas = { /** * Calculate skill level. * @param exp - experience for that skill @@ -3840,7 +3840,7 @@ interface SkillsFormulas { * @returns The calculated exp required. */ calculateExp(skill: number, skillMult?: number): number; -} +}; /** @public */ interface WorkStats { @@ -3859,7 +3859,7 @@ interface WorkStats { * Work formulas * @public */ -interface WorkFormulas { +type WorkFormulas = { crimeSuccessChance(person: Person, crimeType: CrimeType | `${CrimeType}`): number; /** @returns The WorkStats gained when completing one instance of the specified crime. */ crimeGains(person: Person, crimeType: CrimeType | `${CrimeType}`): WorkStats; @@ -3875,13 +3875,13 @@ interface WorkFormulas { factionGains(person: Person, workType: FactionWorkType | `${FactionWorkType}`, favor: number): WorkStats; /** @returns The WorkStats applied every game cycle (200ms) by performing the specified company work. */ companyGains(person: Person, companyName: string, workType: JobName | `${JobName}`, favor: number): WorkStats; -} +}; /** * Reputation formulas * @public */ -interface ReputationFormulas { +type ReputationFormulas = { /** * Calculate the total required amount of faction reputation to reach a target favor. * @param favor - target faction favor. @@ -3902,13 +3902,13 @@ interface ReputationFormulas { * @param player - Player info from {@link NS.getPlayer | getPlayer} */ repFromDonation(amount: number, player: Person): number; -} +}; /** * Hacking formulas * @public */ -interface HackingFormulas { +type HackingFormulas = { /** * Calculate hack chance. * (Ex: 0.25 would indicate a 25% chance of success.) @@ -3968,13 +3968,13 @@ interface HackingFormulas { * @returns The calculated weaken time. */ weakenTime(server: Server, player: Person): number; -} +}; /** * Hacknet Node formulas * @public */ -interface HacknetNodesFormulas { +type HacknetNodesFormulas = { /** * Calculate money gain rate. * @param level - level of the node. @@ -4020,13 +4020,13 @@ interface HacknetNodesFormulas { * @returns An object with all hacknet node constants used by the game. */ constants(): HacknetNodeConstants; -} +}; /** * Hacknet Server formulas * @public */ -interface HacknetServersFormulas { +type HacknetServersFormulas = { /** * Calculate hash gain rate. * @param level - level of the server. @@ -4087,13 +4087,13 @@ interface HacknetServersFormulas { * @returns An object with all hacknet server constants used by the game. */ constants(): HacknetServerConstants; -} +}; /** * Gang formulas * @public */ -interface GangFormulas { +type GangFormulas = { /** * Calculate the wanted penalty. * @param gang - Gang info from {@link Gang.getGangInformation | getGangInformation} @@ -4138,7 +4138,7 @@ interface GangFormulas { * @returns The calculated ascension mult. */ ascensionMultiplier(points: number): number; -} +}; /** * Formulas API @@ -4146,7 +4146,7 @@ interface GangFormulas { * You need Formulas.exe on your home computer to use this API. * @public */ -export interface Formulas { +export type Formulas = { mockServer(): Server; mockPlayer(): Player; mockPerson(): Person; @@ -4164,7 +4164,7 @@ export interface Formulas { gang: GangFormulas; /** Work formulas */ work: WorkFormulas; -} +}; /** @public */ interface Fragment { @@ -4189,7 +4189,7 @@ interface ActiveFragment { * Stanek's Gift API. * @public */ -interface Stanek { +type Stanek = { /** * Stanek's Gift width. * @remarks @@ -4295,7 +4295,7 @@ interface Stanek { * false otherwise. */ acceptGift(): boolean; -} +}; /** @public */ interface InfiltrationReward { @@ -4321,7 +4321,7 @@ interface InfiltrationLocation { * Infiltration API. * @public */ -interface Infiltration { +type Infiltration = { /** * Get all locations that can be infiltrated. * @remarks @@ -4338,13 +4338,13 @@ interface Infiltration { * @returns Infiltration data for given location. */ getInfiltration(location: string): InfiltrationLocation; -} +}; /** * User Interface API. * @public */ -interface UserInterface { +type UserInterface = { /** * Get the current window size * @remarks @@ -4427,7 +4427,7 @@ interface UserInterface { * RAM cost: 0.2 GB */ clearTerminal(): void; -} +}; /** * Collection of all functions passed to scripts @@ -4456,7 +4456,7 @@ interface UserInterface { * {@link https://bitburner.readthedocs.io/en/latest/netscript/netscriptjs.html| ns2 in-game docs} *
*/ -export interface NS { +export type NS = { /** * Namespace for hacknet functions. * @remarks RAM cost: 4 GB @@ -6853,7 +6853,7 @@ export interface NS { getSharePower(): number; enums: NSEnums; -} +}; // BASE ENUMS /** @public */ @@ -7073,7 +7073,7 @@ export type NSEnums = { * @public */ -export interface OfficeAPI { +export type OfficeAPI = { /** * Hire an employee. * @param divisionName - Name of the division @@ -7165,7 +7165,7 @@ export interface OfficeAPI { * @returns Cost of upgrading the office */ getOfficeSizeUpgradeCost(divisionName: string, city: CityName | `${CityName}`, asize: number): number; -} +}; /** * Corporation Warehouse API @@ -7173,7 +7173,7 @@ export interface OfficeAPI { * Requires the Warehouse API upgrade from your corporation. * @public */ -export interface WarehouseAPI { +export type WarehouseAPI = { /** * Set material sell data. * @param divisionName - Name of the division @@ -7396,13 +7396,13 @@ export interface WarehouseAPI { * @returns true if warehouse is present, false if not */ hasWarehouse(divisionName: string, city: CityName | `${CityName}`): boolean; -} +}; /** * Corporation API * @public */ -export interface Corporation extends WarehouseAPI, OfficeAPI { +export type Corporation = { /** Returns whether the player has a corporation. Does not require API access. * @returns whether the player has a corporation */ hasCorporation(): boolean; @@ -7513,7 +7513,8 @@ export interface Corporation extends WarehouseAPI, OfficeAPI { * “Bonus time” makes the game progress faster. * @returns Bonus time for the Corporation mechanic in milliseconds. */ getBonusTime(): number; -} +} & WarehouseAPI & + OfficeAPI; /** Product rating information * @public */ diff --git a/test/jest/Netscript/RamCalculation.test.ts b/test/jest/Netscript/RamCalculation.test.ts index 6baf6c9d0..83cedcc6c 100644 --- a/test/jest/Netscript/RamCalculation.test.ts +++ b/test/jest/Netscript/RamCalculation.test.ts @@ -7,7 +7,7 @@ import { Script } from "../../../src/Script/Script"; import { WorkerScript } from "../../../src/Netscript/WorkerScript"; import { calculateRamUsage } from "../../../src/Script/RamCalculations"; import { ns } from "../../../src/NetscriptFunctions"; -import { ExternalAPI, InternalAPI } from "src/Netscript/APIWrapper"; +import { InternalAPI, NetscriptContext } from "src/Netscript/APIWrapper"; import { Singularity } from "@nsdefs"; type PotentiallyAsyncFunction = (arg?: unknown) => { catch?: PotentiallyAsyncFunction }; @@ -20,15 +20,10 @@ function getFunction(fn: unknown) { function grabCost(ramEntry: RamCostTree[keyof API]) { if (typeof ramEntry === "function") return ramEntry(); if (typeof ramEntry === "number") return ramEntry; - throw new Error("Invalid ramcost"); + throw new Error("Invalid ramcost: " + ramEntry); } -function isRemovedFunction(fn: Function) { - try { - fn(); - } catch { - return true; - } - return false; +function isRemovedFunction(ctx: NetscriptContext, fn: (ctx: NetscriptContext) => (...__: unknown[]) => unknown) { + return /REMOVED FUNCTION/.test(fn(ctx) + ""); } describe("Netscript RAM Calculation/Generation Tests", function () { @@ -112,7 +107,7 @@ describe("Netscript RAM Calculation/Generation Tests", function () { describe("ns", () => { function testLayer( internalLayer: InternalAPI, - externalLayer: ExternalAPI, + externalLayer: API, ramLayer: RamCostTree, path: string[], extraLayerCost: number, @@ -122,18 +117,22 @@ describe("Netscript RAM Calculation/Generation Tests", function () { const newPath = [...path, key as string]; if (typeof val === "function") { // Removed functions have no ram cost and should be skipped. - if (isRemovedFunction(val)) return; + if (isRemovedFunction({ workerScript }, val)) return; const fn = getFunction(externalLayer[key]); const fnName = newPath.join("."); + if (!(key in ramLayer)) { + throw new Error("Missing ramcost for " + fnName); + } const expectedRam = grabCost(ramLayer[key]); it(`${fnName}()`, () => combinedRamCheck(fn, newPath, expectedRam, extraLayerCost)); } //A layer should be the only other option. Hacknet is currently the only layer with a layer cost. - else { + else if (typeof val === "object" && key !== "enums") { //hacknet is currently the only layer with a layer cost. const layerCost = key === "hacknet" ? 4 : 0; testLayer(val as InternalAPI, externalLayer[key], ramLayer[key], newPath, layerCost); } + // Other things like args, enums, etc. have no cost } }); } @@ -149,7 +148,7 @@ describe("Netscript RAM Calculation/Generation Tests", function () { const singObjects = ( Object.entries(ns.singularity) as [keyof Singularity, InternalAPI[keyof Singularity]][] ) - .filter(([_, v]) => typeof v === "function" && !isRemovedFunction(v)) + .filter(([__, v]) => typeof v === "function" && !isRemovedFunction({ workerScript }, v)) .map(([name]) => { return { name,