diff --git a/src/Netscript/APIWrapper.ts b/src/Netscript/APIWrapper.ts new file mode 100644 index 000000000..c968f171e --- /dev/null +++ b/src/Netscript/APIWrapper.ts @@ -0,0 +1,110 @@ +import { getRamCost } from "./RamCostGenerator"; +import type { IPort } from "../NetscriptPort"; +import type { BaseServer } from "../Server/BaseServer"; +import type { WorkerScript } from "./WorkerScript"; +import { makeRuntimeRejectMsg } from "../NetscriptEvaluator"; +import { Player } from "../Player"; + + +// type TargetFn = (...args: [T, ...any[]]) => Promise; +// type WrappedFn = F extends (a: infer A1, ...args: infer U) => Promise ? (a: A1|A1[], ...args: U) => Promise : unknown; +// Need to think more about how to get these types to work +type InternalNetscriptFunction = (ctx: NetscriptContext, ...args: unknown[]) => unknown; +type WrappedNetscriptFunction = (...args: unknown[]) => unknown; +export type InternalNetscriptAPI = { + [string: string]: InternalNetscriptAPI | InternalNetscriptFunction; +} +export type WrappedNetscriptAPI = { + [string: string]: WrappedNetscriptAPI | WrappedNetscriptFunction; +} + +export type NetscriptContext = { + workerScript: WorkerScript; + function: string; + makeRuntimeErrorMsg: (message: string) => string; + log: (message: () => string) => void; + updateDynamicRam: () => void; +}; + +type NetscriptHelpers = { + updateDynamicRam: (fnName: string, ramCost: number) => void; + makeRuntimeErrorMsg: (caller: string, msg: string) => string; + string: (funcName: string, argName: string, v: unknown) => string; + number: (funcName: string, argName: string, v: unknown) => number; + boolean: (v: unknown) => boolean; + getServer: (hostname: string, callingFnName: string) => BaseServer; + checkSingularityAccess: (func: string) => void; + hack: (hostname: any, manual: any, { threads: requestedThreads, stock }?: any) => Promise; + getValidPort: (funcName: string, port: any) => IPort; +} + +function wrapFunction(helpers: NetscriptHelpers, wrappedAPI: any, workerScript: WorkerScript, func: (ctx: NetscriptContext, ...args: unknown[]) => T, ...tree: string[]): void { + const functionName = tree.pop(); + if (typeof functionName !== 'string') { + throw makeRuntimeRejectMsg(workerScript, 'Failure occured while wrapping netscript api'); + } + const ctx = { + workerScript, + function: functionName, + makeRuntimeErrorMsg: (message: string) => { return helpers.makeRuntimeErrorMsg(functionName, message); }, + log: (message: () => string) => { workerScript.log(functionName, message); }, + updateDynamicRam: () => { + helpers.updateDynamicRam(functionName, getRamCost(Player, ...tree, functionName)); + } + }; + function wrappedFunction(...args: unknown[]): T { + return func(ctx, ...args); + } + const parent = getNestedProperty(wrappedAPI, ...tree); + Object.defineProperty(parent, functionName, { + value: wrappedFunction, + writable: true + }); +} + +// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types +export function wrapAPI(helpers: NetscriptHelpers, wrappedAPI: any, workerScript: WorkerScript, namespace: any, ...tree: string[]): WrappedNetscriptAPI { + if (typeof namespace !== 'object') throw new Error('Invalid namespace?'); + for (const property of Object.getOwnPropertyNames(namespace)) { + switch (typeof namespace[property]) { + case 'function': { + wrapFunction(helpers, wrappedAPI, workerScript, namespace[property], ...tree, property); + break; + } + case 'object': { + wrapAPI(helpers, wrappedAPI, workerScript, namespace[property], ...tree, property); + break; + } + default: { + setNestedProperty(wrappedAPI, namespace[property], ...tree, property); + } + } + } + return wrappedAPI; +} + +function setNestedProperty(root: any, value: any, ...tree: string[]): any { + let target = root; + const key = tree.pop(); + if (typeof key !== 'string') { + throw new Error('Failure occured while wrapping netscript api (setNestedProperty)') + } + for (const branch of tree) { + if (target[branch] === undefined) { + target[branch] = {}; + } + target = target[branch]; + } + target[key] = value; +} + +function getNestedProperty(root: any, ...tree: string[]): any { + let target = root; + for (const branch of tree) { + if (target[branch] === undefined) { + target[branch] = {}; + } + target = target[branch]; + } + return target; +} diff --git a/src/NetscriptFunctions.ts b/src/NetscriptFunctions.ts index bc204239a..a24978bb0 100644 --- a/src/NetscriptFunctions.ts +++ b/src/NetscriptFunctions.ts @@ -99,6 +99,7 @@ import { Flags } from "./NetscriptFunctions/Flags"; import { calculateIntelligenceBonus } from "./PersonObjects/formulas/intelligence"; import { CalculateShareMult, StartSharing } from "./NetworkShare/Share"; import { CityName } from "./Locations/data/CityNames"; +import { wrapAPI } from "./Netscript/APIWrapper"; interface NS extends INS { [key: string]: any; @@ -491,7 +492,7 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS { const sleeve = NetscriptSleeve(Player, workerScript, helper); const extra = NetscriptExtra(Player, workerScript, helper); const hacknet = NetscriptHacknet(Player, workerScript, helper); - const stanek = NetscriptStanek(Player, workerScript, helper); + const stanek = wrapAPI(helper, {}, workerScript, NetscriptStanek(Player, workerScript, helper), 'stanek').stanek as unknown as IStanek; const bladeburner = NetscriptBladeburner(Player, workerScript, helper); const codingcontract = NetscriptCodingContract(Player, workerScript, helper); const corporation = NetscriptCorporation(Player, workerScript, helper); diff --git a/src/NetscriptFunctions/Stanek.ts b/src/NetscriptFunctions/Stanek.ts index 6c55e21bd..a361cd27b 100644 --- a/src/NetscriptFunctions/Stanek.ts +++ b/src/NetscriptFunctions/Stanek.ts @@ -2,99 +2,94 @@ import { INetscriptHelper } from "./INetscriptHelper"; import { IPlayer } from "../PersonObjects/IPlayer"; import { WorkerScript } from "../Netscript/WorkerScript"; import { netscriptDelay } from "../NetscriptEvaluator"; -import { getRamCost } from "../Netscript/RamCostGenerator"; import { staneksGift } from "../CotMG/Helper"; import { Fragments, FragmentById } from "../CotMG/Fragment"; import { - Stanek as IStanek, Fragment as IFragment, ActiveFragment as IActiveFragment, } from "../ScriptEditor/NetscriptDefinitions"; import { AugmentationNames } from "../Augmentation/data/AugmentationNames"; +import { NetscriptContext, InternalNetscriptAPI } from "src/Netscript/APIWrapper"; -export function NetscriptStanek(player: IPlayer, workerScript: WorkerScript, helper: INetscriptHelper): IStanek { +export function NetscriptStanek(player: IPlayer, workerScript: WorkerScript, helper: INetscriptHelper): InternalNetscriptAPI { function checkStanekAPIAccess(func: string): void { if (!player.hasAugmentation(AugmentationNames.StaneksGift1, true)) { helper.makeRuntimeErrorMsg(func, "Requires Stanek's Gift installed."); } } - const updateRam = (funcName: string): void => - helper.updateDynamicRam(funcName, getRamCost(player, "stanek", funcName)); - return { - giftWidth: function (): number { - updateRam("giftWidth"); + giftWidth: function (ctx: NetscriptContext): number { + ctx.updateDynamicRam(); checkStanekAPIAccess("giftWidth"); return staneksGift.width(); }, - giftHeight: function (): number { - updateRam("giftHeight"); + giftHeight: function (ctx: NetscriptContext): number { + ctx.updateDynamicRam(); checkStanekAPIAccess("giftHeight"); return staneksGift.height(); }, - chargeFragment: function (_rootX: unknown, _rootY: unknown): Promise { - updateRam("chargeFragment"); + chargeFragment: function (ctx: NetscriptContext, _rootX: unknown, _rootY: unknown): Promise { + ctx.updateDynamicRam(); const rootX = helper.number("stanek.chargeFragment", "rootX", _rootX); const rootY = helper.number("stanek.chargeFragment", "rootY", _rootY); checkStanekAPIAccess("chargeFragment"); const fragment = staneksGift.findFragment(rootX, rootY); - if (!fragment) - throw helper.makeRuntimeErrorMsg("stanek.chargeFragment", `No fragment with root (${rootX}, ${rootY}).`); + if (!fragment) throw ctx.makeRuntimeErrorMsg(`No fragment with root (${rootX}, ${rootY}).`); const time = staneksGift.inBonus() ? 200 : 1000; return netscriptDelay(time, workerScript).then(function () { const charge = staneksGift.charge(player, fragment, workerScript.scriptRef.threads); - workerScript.log("stanek.chargeFragment", () => `Charged fragment for ${charge} charge.`); + ctx.log(() => `Charged fragment for ${charge} charge.`); return Promise.resolve(); }); }, - fragmentDefinitions: function (): IFragment[] { - updateRam("fragmentDefinitions"); + fragmentDefinitions: function (ctx: NetscriptContext): IFragment[] { + ctx.updateDynamicRam(); checkStanekAPIAccess("fragmentDefinitions"); - workerScript.log("stanek.fragmentDefinitions", () => `Returned ${Fragments.length} fragments`); + ctx.log(() => `Returned ${Fragments.length} fragments`); return Fragments.map((f) => f.copy()); }, - activeFragments: function (): IActiveFragment[] { - updateRam("activeFragments"); + activeFragments: function (ctx: NetscriptContext): IActiveFragment[] { + ctx.updateDynamicRam(); checkStanekAPIAccess("activeFragments"); - workerScript.log("stanek.activeFragments", () => `Returned ${staneksGift.fragments.length} fragments`); + ctx.log(() => `Returned ${staneksGift.fragments.length} fragments`); return staneksGift.fragments.map((af) => { return { ...af.copy(), ...af.fragment().copy() }; }); }, - clearGift: function (): void { - updateRam("clearGift"); + clearGift: function (ctx: NetscriptContext): void { + ctx.updateDynamicRam(); checkStanekAPIAccess("clearGift"); - workerScript.log("stanek.clearGift", () => `Cleared Stanek's Gift.`); + ctx.log(() => `Cleared Stanek's Gift.`); staneksGift.clear(); }, - canPlaceFragment: function (_rootX: unknown, _rootY: unknown, _rotation: unknown, _fragmentId: unknown): boolean { - updateRam("canPlaceFragment"); + canPlaceFragment: function (ctx: NetscriptContext, _rootX: unknown, _rootY: unknown, _rotation: unknown, _fragmentId: unknown): boolean { + ctx.updateDynamicRam(); const rootX = helper.number("stanek.canPlaceFragment", "rootX", _rootX); const rootY = helper.number("stanek.canPlaceFragment", "rootY", _rootY); const rotation = helper.number("stanek.canPlaceFragment", "rotation", _rotation); const fragmentId = helper.number("stanek.canPlaceFragment", "fragmentId", _fragmentId); checkStanekAPIAccess("canPlaceFragment"); const fragment = FragmentById(fragmentId); - if (!fragment) throw helper.makeRuntimeErrorMsg("stanek.canPlaceFragment", `Invalid fragment id: ${fragmentId}`); + if (!fragment) throw ctx.makeRuntimeErrorMsg(`Invalid fragment id: ${fragmentId}`); const can = staneksGift.canPlace(rootX, rootY, rotation, fragment); return can; }, - placeFragment: function (_rootX: unknown, _rootY: unknown, _rotation: unknown, _fragmentId: unknown): boolean { - updateRam("placeFragment"); + placeFragment: function (ctx: NetscriptContext, _rootX: unknown, _rootY: unknown, _rotation: unknown, _fragmentId: unknown): boolean { + ctx.updateDynamicRam(); const rootX = helper.number("stanek.placeFragment", "rootX", _rootX); const rootY = helper.number("stanek.placeFragment", "rootY", _rootY); const rotation = helper.number("stanek.placeFragment", "rotation", _rotation); const fragmentId = helper.number("stanek.placeFragment", "fragmentId", _fragmentId); checkStanekAPIAccess("placeFragment"); const fragment = FragmentById(fragmentId); - if (!fragment) throw helper.makeRuntimeErrorMsg("stanek.placeFragment", `Invalid fragment id: ${fragmentId}`); + if (!fragment) throw ctx.makeRuntimeErrorMsg(`Invalid fragment id: ${fragmentId}`); return staneksGift.place(rootX, rootY, rotation, fragment); }, - getFragment: function (_rootX: unknown, _rootY: unknown): IActiveFragment | undefined { - updateRam("getFragment"); + getFragment: function (ctx: NetscriptContext, _rootX: unknown, _rootY: unknown): IActiveFragment | undefined { + ctx.updateDynamicRam(); const rootX = helper.number("stanek.getFragment", "rootX", _rootX); const rootY = helper.number("stanek.getFragment", "rootY", _rootY); checkStanekAPIAccess("getFragment"); @@ -102,8 +97,8 @@ export function NetscriptStanek(player: IPlayer, workerScript: WorkerScript, hel if (fragment !== undefined) return fragment.copy(); return undefined; }, - removeFragment: function (_rootX: unknown, _rootY: unknown): boolean { - updateRam("removeFragment"); + removeFragment: function (ctx: NetscriptContext, _rootX: unknown, _rootY: unknown): boolean { + ctx.updateDynamicRam(); const rootX = helper.number("stanek.removeFragment", "rootX", _rootX); const rootY = helper.number("stanek.removeFragment", "rootY", _rootY); checkStanekAPIAccess("removeFragment");