From 4ca598defb31fe26080cda6cd4f396d02aeec66f Mon Sep 17 00:00:00 2001 From: Snarling <84951833+Snarling@users.noreply.github.com> Date: Mon, 7 Aug 2023 02:38:38 -0400 Subject: [PATCH] API: Fix removed functions (#720) --- src/Netscript/APIWrapper.ts | 44 +++++++++++++++------- src/Netscript/RamCostGenerator.ts | 43 ++++++++++----------- src/NetscriptFunctions.ts | 11 +++--- src/NetscriptFunctions/Corporation.ts | 38 ++++++++++--------- src/NetscriptFunctions/Formulas.ts | 10 ++--- src/NetscriptFunctions/Singularity.ts | 14 ++++--- src/NetscriptFunctions/Sleeve.ts | 12 +++--- test/jest/Netscript/RamCalculation.test.ts | 11 ++---- 8 files changed, 99 insertions(+), 84 deletions(-) diff --git a/src/Netscript/APIWrapper.ts b/src/Netscript/APIWrapper.ts index cfa097bd6..940c25afb 100644 --- a/src/Netscript/APIWrapper.ts +++ b/src/Netscript/APIWrapper.ts @@ -46,7 +46,9 @@ class NSProxyHandler> { 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 }; + if (Object.hasOwn(this.memoed, key)) return Object.getOwnPropertyDescriptor(this.memoed, key); + this.get(__target, key, this); + return Object.getOwnPropertyDescriptor(this.memoed, key); } defineProperty(__target: unknown, __key: unknown, __attrs: unknown): boolean { @@ -62,8 +64,9 @@ class NSProxyHandler> { const ours = this.memoed[key]; if (ours) return ours; - const field = this.ns[key]; - if (!field) return field; + const descriptor = Object.getOwnPropertyDescriptor(this.ns, key); + if (!descriptor) return descriptor; + const field = descriptor.value; if (typeof field === "function") { const arrayPath = [...this.tree, key]; @@ -75,10 +78,11 @@ class NSProxyHandler> { const wrappedFunction = function (...args: unknown[]): unknown { // What remains *must* be called every time. helpers.checkEnvFlags(ctx); - helpers.updateDynamicRam(ctx, getRamCost(...arrayPath)); + helpers.updateDynamicRam(ctx, getRamCost(arrayPath)); return func(...args); }; - return ((this.memoed[key] as APIFn) = wrappedFunction); + Object.defineProperty(this.memoed, key, { ...descriptor, value: wrappedFunction }); + return wrappedFunction; } if (typeof field === "object") { return ((this.memoed[key] as GenericAPI) = NSProxy( @@ -105,12 +109,26 @@ export function NSProxy>( } /** 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) => () => { - throw helpers.makeRuntimeErrorMsg( - ctx, - `Function removed in ${version}. ${replaceMsg ? replacement : `Please use ${replacement} instead.`}`, - "REMOVED FUNCTION", - ); - }; +interface RemovedFunctionInfo { + /** The version in which the function was removed */ + version: string; + /** The replacement function to use, or the entire replacement message if replaceMsg is true. */ + replacement: string; + /** If set, replacement is treated as a full replacement message. */ + replaceMsg?: true; +} +export function setRemovedFunctions(api: object, infos: Record) { + for (const [key, { version, replacement, replaceMsg }] of Object.entries(infos)) { + Object.defineProperty(api, key, { + value: (ctx: NetscriptContext) => () => { + throw helpers.makeRuntimeErrorMsg( + ctx, + `Function removed in ${version}. ${replaceMsg ? replacement : `Please use ${replacement} instead.`}`, + "REMOVED FUNCTION", + ); + }, + configurable: true, + enumerable: false, + }); + } } diff --git a/src/Netscript/RamCostGenerator.ts b/src/Netscript/RamCostGenerator.ts index f73a1ad13..3aa95f4ab 100644 --- a/src/Netscript/RamCostGenerator.ts +++ b/src/Netscript/RamCostGenerator.ts @@ -615,32 +615,27 @@ export const RamCosts: RamCostTree = { }, } as const; -export function getRamCost(...args: string[]): number { - if (args.length === 0) { - throw new Error(`No arguments passed to getRamCost()`); - } +type RamTreeGeneric = { [key: string]: number | (() => number) | RamTreeGeneric | undefined }; - let curr = RamCosts[args[0] as keyof typeof RamCosts]; - for (let i = 1; i < args.length; ++i) { - if (curr == null) { - throw new Error(`Invalid function passed to getRamCost: ${args.join(".")}`); +export function getRamCost(tree: string[], throwOnUndefined = false): number { + if (tree.length === 0) throw new Error(`No arguments passed to getRamCost()`); + + let obj: RamTreeGeneric = RamCosts; + + for (const branch of tree) { + const next = obj[branch]; + if (next === undefined) { + // If no ram cost is defined (e.g. for removed functions), the cost is 0. + const errorText = `No ram cost is defined for (ns.${tree.join(".")})`; + if (throwOnUndefined) throw errorText; + return 0; + } + if (next && typeof next === "object") { + obj = next; + continue; } - const currType = typeof curr; - if (currType === "function" || currType === "number") { - break; - } - - curr = curr[args[i] as keyof typeof curr]; + return typeof next === "function" ? next() : next; } - - if (typeof curr === "number") { - return curr; - } - - if (typeof curr === "function") { - return curr(); - } - - throw new Error(`Invalid function passed to getRamCost: ${args.join(".")}`); + throw new Error(`Tried to get ram cost for ns.${tree.join(".")} but the value was an invalid type`); } diff --git a/src/NetscriptFunctions.ts b/src/NetscriptFunctions.ts index 1ae405eb5..276e4e7c3 100644 --- a/src/NetscriptFunctions.ts +++ b/src/NetscriptFunctions.ts @@ -86,7 +86,7 @@ import { Flags } from "./NetscriptFunctions/Flags"; import { calculateIntelligenceBonus } from "./PersonObjects/formulas/intelligence"; import { CalculateShareMult, StartSharing } from "./NetworkShare/Share"; import { recentScripts } from "./Netscript/RecentScripts"; -import { InternalAPI, removedFunction, NSProxy } from "./Netscript/APIWrapper"; +import { InternalAPI, setRemovedFunctions, NSProxy } from "./Netscript/APIWrapper"; import { INetscriptExtra } from "./NetscriptFunctions/Extra"; import { ScriptDeath } from "./Netscript/ScriptDeath"; import { getBitNodeMultipliers } from "./BitNode/BitNode"; @@ -1764,7 +1764,7 @@ export const ns: InternalAPI = { }), getFunctionRamCost: (ctx) => (_name) => { const name = helpers.string(ctx, "name", _name); - return getRamCost(...name.split(".")); + return getRamCost(name.split("."), true); }, tprintRaw: () => (value) => { Terminal.printRaw(wrapUserNode(value)); @@ -1775,9 +1775,10 @@ export const ns: InternalAPI = { flags: Flags, ...NetscriptExtra(), }; -// Object.assign to bypass ts for removedFunctions which have no documentation or ramcost -Object.assign(ns, { - getServerRam: removedFunction("v2.2.0", "getServerMaxRam and getServerUsedRam"), + +// Removed functions +setRemovedFunctions(ns, { + getServerRam: { version: "2.2.0", replacement: "getServerMaxRam and getServerUsedRam" }, }); export function NetscriptFunctions(ws: WorkerScript): NSFull { diff --git a/src/NetscriptFunctions/Corporation.ts b/src/NetscriptFunctions/Corporation.ts index 549be63a0..0c1f58eb3 100644 --- a/src/NetscriptFunctions/Corporation.ts +++ b/src/NetscriptFunctions/Corporation.ts @@ -57,7 +57,7 @@ import * as corpConstants from "../Corporation/data/Constants"; import { ResearchMap } from "../Corporation/ResearchMap"; import { Factions } from "../Faction/Factions"; import { currentNodeMults } from "../BitNode/BitNodeMultipliers"; -import { InternalAPI, NetscriptContext, removedFunction } from "../Netscript/APIWrapper"; +import { InternalAPI, NetscriptContext, setRemovedFunctions } from "../Netscript/APIWrapper"; import { helpers } from "../Netscript/NetscriptHelpers"; import { getEnumHelper } from "../utils/EnumHelper"; import { MaterialInfo } from "../Corporation/MaterialInfo"; @@ -841,22 +841,26 @@ export function NetscriptCorporation(): InternalAPI { }, }; - // TODO 3.0: Remove these removedFunctions warnings. - Object.assign(corpFunctions, { - assignJob: removedFunction( - "v2.2.0", - "Removed due to employees no longer being objects. Use ns.corporation.setAutoJobAssignment instead.", - true, - ), - getEmployee: removedFunction("v2.2.0", "Removed due to employees no longer being individual objects.", true), - getExpandCityCost: removedFunction("v2.2.0", "corporation.getConstants().officeInitialCost"), - getExpandIndustryCost: removedFunction("v2.2.0", "corporation.getIndustryData"), - getIndustryTypes: removedFunction("v2.2.0", "corporation.getConstants().industryNames"), - getMaterialNames: removedFunction("v2.2.0", "corporation.getConstants().materialNames"), - getPurchaseWarehouseCost: removedFunction("v2.2.0", "corporation.getConstants().warehouseInitialCost"), - getResearchNames: removedFunction("v2.2.0", "corporation.getConstants().researchNames"), - getUnlockables: removedFunction("v2.2.0", "corporation.getConstants().unlockNames"), - getUpgradeNames: removedFunction("v2.2.0", "corporation.getConstants().upgradeNames"), + // Removed functions + setRemovedFunctions(corpFunctions, { + assignJob: { + version: "2.2.0", + replacement: "Removed due to employees no longer being objects. Use ns.corporation.setAutoJobAssignment instead.", + replaceMsg: true, + }, + getEmployee: { + version: "2.2.0", + replacement: "Removed due to employees no longer being individual objects.", + replaceMsg: true, + }, + getExpandCityCost: { version: "2.2.0", replacement: "corporation.getConstants().officeInitialCost" }, + getExpandIndustryCost: { version: "2.2.0", replacement: "corporation.getIndustryData" }, + getIndustryTypes: { version: "2.2.0", replacement: "corporation.getConstants().industryNames" }, + getMaterialNames: { version: "2.2.0", replacement: "corporation.getConstants().materialNames" }, + getPurchaseWarehouseCost: { version: "2.2.0", replacement: "corporation.getConstants().warehouseInitialCost" }, + getResearchNames: { version: "2.2.0", replacement: "corporation.getConstants().researchNames" }, + getUnlockables: { version: "2.2.0", replacement: "corporation.getConstants().unlockNames" }, + getUpgradeNames: { version: "2.2.0", replacement: "corporation.getConstants().upgradeNames" }, }); return corpFunctions; } diff --git a/src/NetscriptFunctions/Formulas.ts b/src/NetscriptFunctions/Formulas.ts index d56b9d7ec..b35bca832 100644 --- a/src/NetscriptFunctions/Formulas.ts +++ b/src/NetscriptFunctions/Formulas.ts @@ -46,7 +46,7 @@ import { } from "../Gang/formulas/formulas"; import { favorToRep as calculateFavorToRep, repToFavor as calculateRepToFavor } from "../Faction/formulas/favor"; import { repFromDonation } from "../Faction/formulas/donation"; -import { InternalAPI, NetscriptContext, removedFunction } from "../Netscript/APIWrapper"; +import { InternalAPI, NetscriptContext, setRemovedFunctions } from "../Netscript/APIWrapper"; import { helpers } from "../Netscript/NetscriptHelpers"; import { calculateCrimeWorkStats } from "../Work/Formulas"; import { calculateCompanyWorkStats } from "../Work/Formulas"; @@ -427,10 +427,10 @@ export function NetscriptFormulas(): InternalAPI { }, }, }; - // Removed undocumented functions added using Object.assign because typescript. - // TODO: Remove these at 3.0 - Object.assign(formulasFunctions.work, { - classGains: removedFunction("2.2.0", "formulas.work.universityGains or formulas.work.gymGains"), + + // Removed functions + setRemovedFunctions(formulasFunctions.work, { + classGains: { version: "2.2.0", replacement: "formulas.work.universityGains or formulas.work.gymGains" }, }); return formulasFunctions; } diff --git a/src/NetscriptFunctions/Singularity.ts b/src/NetscriptFunctions/Singularity.ts index ea89f98a2..d52bf8dc3 100644 --- a/src/NetscriptFunctions/Singularity.ts +++ b/src/NetscriptFunctions/Singularity.ts @@ -40,7 +40,7 @@ import { Server } from "../Server/Server"; import { netscriptCanHack } from "../Hacking/netscriptCanHack"; import { FactionInfos } from "../Faction/FactionInfo"; import { donate, repNeededToDonate } from "../Faction/formulas/donation"; -import { InternalAPI, removedFunction } from "../Netscript/APIWrapper"; +import { InternalAPI, setRemovedFunctions } from "../Netscript/APIWrapper"; import { enterBitNode } from "../RedPill"; import { ClassWork } from "../Work/ClassWork"; import { CreateProgramWork, isCreateProgramWork } from "../Work/CreateProgramWork"; @@ -1204,11 +1204,13 @@ export function NetscriptSingularity(): InternalAPI { return canGetBonus(); }, }; - Object.assign(singularityAPI, { - getAugmentationCost: removedFunction( - "v2.2.0", - "singularity.getAugmentationPrice and singularity.getAugmentationRepReq", - ), + + // Removed functions + setRemovedFunctions(singularityAPI, { + getAugmentationCost: { + version: "2.2.0", + replacement: "singularity.getAugmentationPrice and singularity.getAugmentationRepReq", + }, }); return singularityAPI; } diff --git a/src/NetscriptFunctions/Sleeve.ts b/src/NetscriptFunctions/Sleeve.ts index c7c713cc1..493ac9c86 100644 --- a/src/NetscriptFunctions/Sleeve.ts +++ b/src/NetscriptFunctions/Sleeve.ts @@ -5,7 +5,7 @@ import { Player } from "@player"; import { Augmentations } from "../Augmentation/Augmentations"; import { findCrime } from "../Crime/CrimeHelpers"; import { getEnumHelper } from "../utils/EnumHelper"; -import { InternalAPI, NetscriptContext, removedFunction } from "../Netscript/APIWrapper"; +import { InternalAPI, NetscriptContext, setRemovedFunctions } from "../Netscript/APIWrapper"; import { isSleeveBladeburnerWork } from "../PersonObjects/Sleeve/Work/SleeveBladeburnerWork"; import { isSleeveFactionWork } from "../PersonObjects/Sleeve/Work/SleeveFactionWork"; import { isSleeveCompanyWork } from "../PersonObjects/Sleeve/Work/SleeveCompanyWork"; @@ -262,11 +262,11 @@ export function NetscriptSleeve(): InternalAPI { return Player.sleeves[sleeveNumber].bladeburner(action, contract); }, }; - // Removed undocumented functions added using Object.assign because typescript. - // TODO: Remove these at 3.0 - Object.assign(sleeveFunctions, { - getSleeveStats: removedFunction("v2.2.0", "sleeve.getSleeve"), - getInformation: removedFunction("v2.2.0", "sleeve.getSleeve"), + + // Removed functions + setRemovedFunctions(sleeveFunctions, { + getSleeveStats: { version: "2.2.0", replacement: "sleeve.getSleeve" }, + getInformation: { version: "2.2.0", replacement: "sleeve.getSleeve" }, }); return sleeveFunctions; } diff --git a/test/jest/Netscript/RamCalculation.test.ts b/test/jest/Netscript/RamCalculation.test.ts index 2c107cf0d..f3045e549 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 { InternalAPI, NetscriptContext } from "src/Netscript/APIWrapper"; +import { InternalAPI } from "src/Netscript/APIWrapper"; import { Singularity } from "@nsdefs"; type PotentiallyAsyncFunction = (arg?: unknown) => { catch?: PotentiallyAsyncFunction }; @@ -22,9 +22,6 @@ function grabCost(ramEntry: RamCostTree[keyof API]) { if (typeof ramEntry === "number") return ramEntry; throw new Error("Invalid ramcost: " + ramEntry); } -function isRemovedFunction(ctx: NetscriptContext, fn: (ctx: NetscriptContext) => (...__: unknown[]) => unknown) { - return /REMOVED FUNCTION/.test(fn(ctx) + ""); -} describe("Netscript RAM Calculation/Generation Tests", function () { Player.sourceFiles.set(4, 3); @@ -74,7 +71,7 @@ describe("Netscript RAM Calculation/Generation Tests", function () { const fnName = fnPath[fnPath.length - 1]; //check imported getRamCost fn vs. expected ram from test - expect(getRamCost(...fnPath)).toEqual(expectedRamCost); + expect(getRamCost(fnPath, true)).toEqual(expectedRamCost); // Static ram check const staticCost = calculateRamUsage(code, new Map()).cost; @@ -118,8 +115,6 @@ describe("Netscript RAM Calculation/Generation Tests", function () { for (const [key, val] of Object.entries(internalLayer) as [keyof API, InternalAPI[keyof API]][]) { const newPath = [...path, key as string]; if (typeof val === "function") { - // Removed functions have no ram cost and should be skipped. - if (isRemovedFunction({ workerScript }, val)) return; const fn = getFunction(externalLayer[key]); const fnName = newPath.join("."); if (!(key in ramLayer)) { @@ -150,7 +145,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({ workerScript }, v)) + .filter(([__, v]) => typeof v === "function") .map(([name]) => { return { name,