NETSCRIPT: More ns Proxy changes (#297)

This commit is contained in:
David Walker 2023-01-05 17:41:24 -08:00 committed by GitHub
parent 3281b785ce
commit 4eef9eec03
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 148 additions and 143 deletions

4
package-lock.json generated

@ -1,12 +1,12 @@
{ {
"name": "bitburner", "name": "bitburner",
"version": "2.2.0", "version": "2.2.2dev",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "bitburner", "name": "bitburner",
"version": "2.2.0", "version": "2.2.2dev",
"hasInstallScript": true, "hasInstallScript": true,
"license": "SEE LICENSE IN license.txt", "license": "SEE LICENSE IN license.txt",
"dependencies": { "dependencies": {

@ -1,7 +1,7 @@
{ {
"name": "bitburner", "name": "bitburner",
"license": "SEE LICENSE IN license.txt", "license": "SEE LICENSE IN license.txt",
"version": "2.2.0", "version": "2.2.2dev",
"main": "electron-main.js", "main": "electron-main.js",
"author": { "author": {
"name": "Daniel Xie, Olivier Gagnon, et al." "name": "Daniel Xie, Olivier Gagnon, et al."

@ -3,18 +3,14 @@ import type { WorkerScript } from "./WorkerScript";
import { helpers } from "./NetscriptHelpers"; import { helpers } from "./NetscriptHelpers";
/** Permissive type for the documented API functions */ /** Permissive type for the documented API functions */
type APIFn = (...args: any[]) => void; type APIFn = (...args: any[]) => unknown;
/** Type for the actual wrapped function given to the player */
type WrappedFn = (...args: unknown[]) => unknown;
/** Type for internal, unwrapped ctx function that produces an APIFunction */ /** Type for internal, unwrapped ctx function that produces an APIFunction */
type InternalFn<F extends APIFn> = (ctx: NetscriptContext) => ((...args: unknown[]) => ReturnType<F>) & F; type InternalFn<F extends APIFn> = (ctx: NetscriptContext) => ((...args: unknown[]) => ReturnType<F>) & 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. // 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) // They do in fact exist on the external API (but are absent on the internal API and ramcost tree)
export type ExternalAPI<API> = {
[key in keyof API]: API[key] extends APIFn ? WrappedFn : ExternalAPI<API[key]>;
};
export type InternalAPI<API> = { export type InternalAPI<API> = {
[key in keyof API]: API[key] extends APIFn ? InternalFn<API[key]> : InternalAPI<API[key]>; [key in keyof API]: API[key] extends APIFn ? InternalFn<API[key]> : InternalAPI<API[key]>;
}; };
@ -25,70 +21,91 @@ export type NetscriptContext = {
functionPath: string; functionPath: string;
}; };
export function NSProxy<API>( class NSProxyHandler<API extends GenericAPI> {
ws: WorkerScript, ns: API;
ns: InternalAPI<API>, ws: WorkerScript;
tree: string[], tree: string[];
additionalData?: Record<string, unknown>, additionalData: Record<string, unknown>;
): ExternalAPI<API> { memoed: API = {} as API;
const memoed: ExternalAPI<API> = Object.assign({} as ExternalAPI<API>, additionalData ?? {});
const handler = { constructor(ws: WorkerScript, ns: API, tree: string[], additionalData: Record<string, unknown>) {
has(__target: unknown, key: string) { this.ns = ns;
return Reflect.has(ns, key); this.ws = ws;
}, this.tree = tree;
ownKeys(__target: unknown) { this.additionalData = additionalData;
return Reflect.ownKeys(ns); Object.assign(this.memoed, additionalData);
}, }
getOwnPropertyDescriptor(__target: unknown, key: keyof API & string) {
if (!Reflect.has(ns, key)) return undefined; 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 }; return { value: this.get(__target, key, this), configurable: true, enumerable: true, writable: false };
}, }
defineProperty(__target: unknown, __key: unknown, __attrs: unknown) {
defineProperty(__target: unknown, __key: unknown, __attrs: unknown): boolean {
throw new TypeError("ns instances are not modifiable!"); throw new TypeError("ns instances are not modifiable!");
}, }
set(__target: unknown, __key: unknown, __attrs: unknown) {
set(__target: unknown, __key: unknown, __attrs: unknown): boolean {
// Redundant with defineProperty, but we'll be explicit
throw new TypeError("ns instances are not modifiable!"); throw new TypeError("ns instances are not modifiable!");
}, }
get(__target: unknown, key: keyof API & string, __receiver: any) { get(__target: unknown, key: keyof API & string, __receiver: any) {
const ours = memoed[key]; const ours = this.memoed[key];
if (ours) return ours; if (ours) return ours;
const field = ns[key]; const field = this.ns[key];
if (!field) return field; if (!field) return field;
if (typeof field === "function") { if (typeof field === "function") {
const arrayPath = [...tree, key]; const arrayPath = [...this.tree, key];
const functionPath = arrayPath.join("."); 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 { const wrappedFunction = function (...args: unknown[]): unknown {
const ctx = { workerScript: ws, function: key, functionPath }; // What remains *must* be called every time.
const func = field(ctx); //Allows throwing before ram check, for removedFunction
helpers.checkEnvFlags(ctx); helpers.checkEnvFlags(ctx);
helpers.updateDynamicRam(ctx, getRamCost(...tree, key)); helpers.updateDynamicRam(ctx, getRamCost(...arrayPath));
return func(...args); return func(...args);
}; };
return ((memoed[key] as WrappedFn) = wrappedFunction); return ((this.memoed[key] as APIFn) = wrappedFunction);
} }
if (typeof field === "object") { if (typeof field === "object") {
// TODO unplanned: Make this work generically return ((this.memoed[key] as GenericAPI) = NSProxy(this.ws, field as InternalAPI<GenericAPI>, [
return ((memoed[key] as ExternalAPI<API[keyof API]>) = NSProxy(ws, field as InternalAPI<API[keyof API]>, [ ...this.tree,
...tree,
key, key,
])); ]));
} }
console.warn(`Unexpected data while wrapping API.`, "tree:", tree, "key:", key, "field:", field); console.warn(`Unexpected data while wrapping API.`, "tree:", this.tree, "key:", key, "field:", field);
throw new Error("Error while wrapping netscript API. See console."); throw new Error("Error while wrapping netscript API. See console.");
}, }
}; }
export function NSProxy<API extends GenericAPI>(
ws: WorkerScript,
ns: InternalAPI<API>,
tree: string[],
additionalData: Record<string, unknown> = {},
): API {
const handler = new NSProxyHandler(ws, ns, tree, additionalData);
// We target an empty Object, so that unproxied methods don't do anything. // 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. // We *can't* freeze the target, because it would break invariants on ownKeys.
return new Proxy({}, handler) as ExternalAPI<API>; return new Proxy({}, handler) as API;
} }
/** Specify when a function was removed from the game, and its replacement function. */ /** Specify when a function was removed from the game, and its replacement function. */
export function removedFunction(version: string, replacement: string, replaceMsg?: boolean) { export function removedFunction(version: string, replacement: string, replaceMsg?: boolean) {
return (ctx: NetscriptContext) => { return (ctx: NetscriptContext) => () => {
throw helpers.makeRuntimeErrorMsg( throw helpers.makeRuntimeErrorMsg(
ctx, ctx,
`Function removed in ${version}. ${replaceMsg ? replacement : `Please use ${replacement} instead.`}`, `Function removed in ${version}. ${replaceMsg ? replacement : `Please use ${replacement} instead.`}`,

@ -1,5 +1,4 @@
import { NSFull } from "../NetscriptFunctions"; import { NSFull } from "../NetscriptFunctions";
import { ExternalAPI } from "./APIWrapper";
/** /**
* The environment in which a script runs. The environment holds * The environment in which a script runs. The environment holds
@ -14,5 +13,5 @@ export class Environment {
runningFn = ""; runningFn = "";
/** Environment variables (currently only Netscript functions) */ /** Environment variables (currently only Netscript functions) */
vars: ExternalAPI<NSFull> | null = null; vars: NSFull | null = null;
} }

@ -16,7 +16,6 @@ import { GetServer } from "../Server/AllServers";
import { BaseServer } from "../Server/BaseServer"; import { BaseServer } from "../Server/BaseServer";
import { ScriptDeath } from "./ScriptDeath"; import { ScriptDeath } from "./ScriptDeath";
import { ScriptArg } from "./ScriptArg"; import { ScriptArg } from "./ScriptArg";
import { ExternalAPI } from "./APIWrapper";
import { NSFull } from "../NetscriptFunctions"; import { NSFull } from "../NetscriptFunctions";
export class WorkerScript { export class WorkerScript {
@ -84,11 +83,7 @@ export class WorkerScript {
/** Function called when the script ends. */ /** Function called when the script ends. */
atExit?: () => void; atExit?: () => void;
constructor( constructor(runningScriptObj: RunningScript, pid: number, nsFuncsGenerator?: (ws: WorkerScript) => NSFull) {
runningScriptObj: RunningScript,
pid: number,
nsFuncsGenerator?: (ws: WorkerScript) => ExternalAPI<NSFull>,
) {
this.name = runningScriptObj.filename; this.name = runningScriptObj.filename;
this.hostname = runningScriptObj.server; this.hostname = runningScriptObj.server;

@ -72,7 +72,7 @@ import { Flags } from "./NetscriptFunctions/Flags";
import { calculateIntelligenceBonus } from "./PersonObjects/formulas/intelligence"; import { calculateIntelligenceBonus } from "./PersonObjects/formulas/intelligence";
import { CalculateShareMult, StartSharing } from "./NetworkShare/Share"; import { CalculateShareMult, StartSharing } from "./NetworkShare/Share";
import { recentScripts } from "./Netscript/RecentScripts"; 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 { INetscriptExtra } from "./NetscriptFunctions/Extra";
import { ScriptDeath } from "./Netscript/ScriptDeath"; import { ScriptDeath } from "./Netscript/ScriptDeath";
import { getBitNodeMultipliers } from "./BitNode/BitNode"; import { getBitNodeMultipliers } from "./BitNode/BitNode";
@ -1894,7 +1894,7 @@ Object.assign(ns, {
getServerRam: removedFunction("v2.2.0", "getServerMaxRam and getServerUsedRam"), getServerRam: removedFunction("v2.2.0", "getServerMaxRam and getServerUsedRam"),
}); });
export function NetscriptFunctions(ws: WorkerScript): ExternalAPI<NSFull> { export function NetscriptFunctions(ws: WorkerScript): NSFull {
return NSProxy(ws, ns, [], { args: ws.args.slice(), pid: ws.pid, enums }); return NSProxy(ws, ns, [], { args: ws.args.slice(), pid: ws.pid, enums });
} }

@ -7,7 +7,7 @@ import { InternalAPI } from "../Netscript/APIWrapper";
import { helpers } from "../Netscript/NetscriptHelpers"; import { helpers } from "../Netscript/NetscriptHelpers";
import { Terminal } from "../Terminal"; import { Terminal } from "../Terminal";
export interface INetscriptExtra { export type INetscriptExtra = {
heart: { heart: {
break(): number; break(): number;
}; };
@ -18,7 +18,7 @@ export interface INetscriptExtra {
rainbow(guess: string): void; rainbow(guess: string): void;
iKnowWhatImDoing(): void; iKnowWhatImDoing(): void;
printRaw(value: React.ReactNode): void; printRaw(value: React.ReactNode): void;
} };
export function NetscriptExtra(): InternalAPI<INetscriptExtra> { export function NetscriptExtra(): InternalAPI<INetscriptExtra> {
return { return {

@ -83,11 +83,6 @@ async function startNetscript1Script(workerScript: WorkerScript): Promise<void>
type BasicObject = Record<string, any>; type BasicObject = Record<string, any>;
const wrappedNS = NetscriptFunctions(workerScript); const wrappedNS = NetscriptFunctions(workerScript);
function wrapNS1Layer(int: Interpreter, intLayer: unknown, nsLayer = wrappedNS as BasicObject) { 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)) { for (const [name, entry] of Object.entries(nsLayer)) {
if (typeof entry === "function") { if (typeof entry === "function") {
const wrapper = async (...args: unknown[]) => { const wrapper = async (...args: unknown[]) => {

@ -1,8 +1,7 @@
import { NSFull } from "../NetscriptFunctions"; import { NSFull } from "../NetscriptFunctions";
import { ExternalAPI } from "../Netscript/APIWrapper";
import { AutocompleteData } from "@nsdefs"; import { AutocompleteData } from "@nsdefs";
export interface ScriptModule { export interface ScriptModule {
main?: (ns: ExternalAPI<NSFull>) => unknown; main?: (ns: NSFull) => unknown;
autocomplete?: (data: AutocompleteData, flags: string[]) => unknown; autocomplete?: (data: AutocompleteData, flags: string[]) => unknown;
} }

@ -1000,7 +1000,7 @@ interface NetscriptPort {
* Stock market API * Stock market API
* @public * @public
*/ */
export interface TIX { export type TIX = {
/** /**
* Returns true if the player has access to a WSE Account * Returns true if the player has access to a WSE Account
* @remarks RAM cost: 0.05 GB * @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. * @returns True if you successfully purchased it or if you already have access, false otherwise.
*/ */
purchaseTixApi(): boolean; purchaseTixApi(): boolean;
} };
/** /**
* Singularity API * Singularity API
@ -1448,7 +1448,7 @@ export interface TIX {
* Source-File 4 levels. * Source-File 4 levels.
* @public * @public
*/ */
export interface Singularity { export type Singularity = {
/** /**
* Backup game save. * Backup game save.
* @remarks * @remarks
@ -2292,7 +2292,7 @@ export interface Singularity {
* @returns - An object representing the current work. Fields depend on the kind of work. * @returns - An object representing the current work. Fields depend on the kind of work.
*/ */
getCurrentWork(): any | null; getCurrentWork(): any | null;
} };
/** /**
* Hacknet API * Hacknet API
@ -2300,7 +2300,7 @@ export interface Singularity {
* Not all these functions are immediately available. * Not all these functions are immediately available.
* @public * @public
*/ */
export interface Hacknet { export type Hacknet = {
/** /**
* Get the number of hacknet nodes you own. * Get the number of hacknet nodes you own.
* @remarks * @remarks
@ -2651,7 +2651,7 @@ export interface Hacknet {
* @returns Multiplier. * @returns Multiplier.
*/ */
getTrainingMult(): number; getTrainingMult(): number;
} };
/** /**
* Bladeburner API * Bladeburner API
@ -2660,7 +2660,7 @@ export interface Hacknet {
* or have Source-File 7 in order to use this API. * or have Source-File 7 in order to use this API.
* @public * @public
*/ */
export interface Bladeburner { export type Bladeburner = {
/** /**
* List all contracts. * List all contracts.
* @remarks * @remarks
@ -3148,13 +3148,13 @@ export interface Bladeburner {
* *
* @returns whether player is a member of bladeburner division. */ * @returns whether player is a member of bladeburner division. */
inBladeburner(): boolean; inBladeburner(): boolean;
} };
/** /**
* Coding Contract API * Coding Contract API
* @public * @public
*/ */
export interface CodingContract { export type CodingContract = {
/** /**
* Attempts a coding contract, returning a reward string on success or empty string on failure. * Attempts a coding contract, returning a reward string on success or empty string on failure.
* @remarks * @remarks
@ -3259,7 +3259,7 @@ export interface CodingContract {
* RAM cost: 2 GB * RAM cost: 2 GB
*/ */
getContractTypes(): string[]; getContractTypes(): string[];
} };
/** /**
* Gang API * 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. * If you are not in BitNode-2, then you must have Source-File 2 in order to use this API.
* @public * @public
*/ */
export interface Gang { export type Gang = {
/** /**
* Create a gang. * Create a gang.
* @remarks * @remarks
@ -3526,7 +3526,7 @@ export interface Gang {
* @returns Bonus time for the Gang mechanic in milliseconds. * @returns Bonus time for the Gang mechanic in milliseconds.
*/ */
getBonusTime(): number; getBonusTime(): number;
} };
/** /**
* Sleeve API * 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. * If you are not in BitNode-10, then you must have Source-File 10 in order to use this API.
* @public * @public
*/ */
export interface Sleeve { export type Sleeve = {
/** /**
* Get the number of sleeves you own. * Get the number of sleeves you own.
* @remarks * @remarks
@ -3764,7 +3764,7 @@ export interface Sleeve {
* @returns True if the sleeve started working out, false otherwise. * @returns True if the sleeve started working out, false otherwise.
*/ */
setToBladeburnerAction(sleeveNumber: number, action: string, contract?: string): boolean; setToBladeburnerAction(sleeveNumber: number, action: string, contract?: string): boolean;
} };
/** /**
* Grafting API * Grafting API
@ -3772,7 +3772,7 @@ export interface Sleeve {
* This API requires Source-File 10 to use. * This API requires Source-File 10 to use.
* @public * @public
*/ */
export interface Grafting { export type Grafting = {
/** /**
* Retrieve the grafting cost of an aug. * Retrieve the grafting cost of an aug.
* @remarks * @remarks
@ -3819,13 +3819,13 @@ export interface Grafting {
* @throws Will error if called while you are not in New Tokyo. * @throws Will error if called while you are not in New Tokyo.
*/ */
graftAugmentation(augName: string, focus?: boolean): boolean; graftAugmentation(augName: string, focus?: boolean): boolean;
} };
/** /**
* Skills formulas * Skills formulas
* @public * @public
*/ */
interface SkillsFormulas { type SkillsFormulas = {
/** /**
* Calculate skill level. * Calculate skill level.
* @param exp - experience for that skill * @param exp - experience for that skill
@ -3840,7 +3840,7 @@ interface SkillsFormulas {
* @returns The calculated exp required. * @returns The calculated exp required.
*/ */
calculateExp(skill: number, skillMult?: number): number; calculateExp(skill: number, skillMult?: number): number;
} };
/** @public */ /** @public */
interface WorkStats { interface WorkStats {
@ -3859,7 +3859,7 @@ interface WorkStats {
* Work formulas * Work formulas
* @public * @public
*/ */
interface WorkFormulas { type WorkFormulas = {
crimeSuccessChance(person: Person, crimeType: CrimeType | `${CrimeType}`): number; crimeSuccessChance(person: Person, crimeType: CrimeType | `${CrimeType}`): number;
/** @returns The WorkStats gained when completing one instance of the specified crime. */ /** @returns The WorkStats gained when completing one instance of the specified crime. */
crimeGains(person: Person, crimeType: CrimeType | `${CrimeType}`): WorkStats; crimeGains(person: Person, crimeType: CrimeType | `${CrimeType}`): WorkStats;
@ -3875,13 +3875,13 @@ interface WorkFormulas {
factionGains(person: Person, workType: FactionWorkType | `${FactionWorkType}`, favor: number): WorkStats; factionGains(person: Person, workType: FactionWorkType | `${FactionWorkType}`, favor: number): WorkStats;
/** @returns The WorkStats applied every game cycle (200ms) by performing the specified company work. */ /** @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; companyGains(person: Person, companyName: string, workType: JobName | `${JobName}`, favor: number): WorkStats;
} };
/** /**
* Reputation formulas * Reputation formulas
* @public * @public
*/ */
interface ReputationFormulas { type ReputationFormulas = {
/** /**
* Calculate the total required amount of faction reputation to reach a target favor. * Calculate the total required amount of faction reputation to reach a target favor.
* @param favor - target faction favor. * @param favor - target faction favor.
@ -3902,13 +3902,13 @@ interface ReputationFormulas {
* @param player - Player info from {@link NS.getPlayer | getPlayer} * @param player - Player info from {@link NS.getPlayer | getPlayer}
*/ */
repFromDonation(amount: number, player: Person): number; repFromDonation(amount: number, player: Person): number;
} };
/** /**
* Hacking formulas * Hacking formulas
* @public * @public
*/ */
interface HackingFormulas { type HackingFormulas = {
/** /**
* Calculate hack chance. * Calculate hack chance.
* (Ex: 0.25 would indicate a 25% chance of success.) * (Ex: 0.25 would indicate a 25% chance of success.)
@ -3968,13 +3968,13 @@ interface HackingFormulas {
* @returns The calculated weaken time. * @returns The calculated weaken time.
*/ */
weakenTime(server: Server, player: Person): number; weakenTime(server: Server, player: Person): number;
} };
/** /**
* Hacknet Node formulas * Hacknet Node formulas
* @public * @public
*/ */
interface HacknetNodesFormulas { type HacknetNodesFormulas = {
/** /**
* Calculate money gain rate. * Calculate money gain rate.
* @param level - level of the node. * @param level - level of the node.
@ -4020,13 +4020,13 @@ interface HacknetNodesFormulas {
* @returns An object with all hacknet node constants used by the game. * @returns An object with all hacknet node constants used by the game.
*/ */
constants(): HacknetNodeConstants; constants(): HacknetNodeConstants;
} };
/** /**
* Hacknet Server formulas * Hacknet Server formulas
* @public * @public
*/ */
interface HacknetServersFormulas { type HacknetServersFormulas = {
/** /**
* Calculate hash gain rate. * Calculate hash gain rate.
* @param level - level of the server. * @param level - level of the server.
@ -4087,13 +4087,13 @@ interface HacknetServersFormulas {
* @returns An object with all hacknet server constants used by the game. * @returns An object with all hacknet server constants used by the game.
*/ */
constants(): HacknetServerConstants; constants(): HacknetServerConstants;
} };
/** /**
* Gang formulas * Gang formulas
* @public * @public
*/ */
interface GangFormulas { type GangFormulas = {
/** /**
* Calculate the wanted penalty. * Calculate the wanted penalty.
* @param gang - Gang info from {@link Gang.getGangInformation | getGangInformation} * @param gang - Gang info from {@link Gang.getGangInformation | getGangInformation}
@ -4138,7 +4138,7 @@ interface GangFormulas {
* @returns The calculated ascension mult. * @returns The calculated ascension mult.
*/ */
ascensionMultiplier(points: number): number; ascensionMultiplier(points: number): number;
} };
/** /**
* Formulas API * Formulas API
@ -4146,7 +4146,7 @@ interface GangFormulas {
* You need Formulas.exe on your home computer to use this API. * You need Formulas.exe on your home computer to use this API.
* @public * @public
*/ */
export interface Formulas { export type Formulas = {
mockServer(): Server; mockServer(): Server;
mockPlayer(): Player; mockPlayer(): Player;
mockPerson(): Person; mockPerson(): Person;
@ -4164,7 +4164,7 @@ export interface Formulas {
gang: GangFormulas; gang: GangFormulas;
/** Work formulas */ /** Work formulas */
work: WorkFormulas; work: WorkFormulas;
} };
/** @public */ /** @public */
interface Fragment { interface Fragment {
@ -4189,7 +4189,7 @@ interface ActiveFragment {
* Stanek's Gift API. * Stanek's Gift API.
* @public * @public
*/ */
interface Stanek { type Stanek = {
/** /**
* Stanek's Gift width. * Stanek's Gift width.
* @remarks * @remarks
@ -4295,7 +4295,7 @@ interface Stanek {
* false otherwise. * false otherwise.
*/ */
acceptGift(): boolean; acceptGift(): boolean;
} };
/** @public */ /** @public */
interface InfiltrationReward { interface InfiltrationReward {
@ -4321,7 +4321,7 @@ interface InfiltrationLocation {
* Infiltration API. * Infiltration API.
* @public * @public
*/ */
interface Infiltration { type Infiltration = {
/** /**
* Get all locations that can be infiltrated. * Get all locations that can be infiltrated.
* @remarks * @remarks
@ -4338,13 +4338,13 @@ interface Infiltration {
* @returns Infiltration data for given location. * @returns Infiltration data for given location.
*/ */
getInfiltration(location: string): InfiltrationLocation; getInfiltration(location: string): InfiltrationLocation;
} };
/** /**
* User Interface API. * User Interface API.
* @public * @public
*/ */
interface UserInterface { type UserInterface = {
/** /**
* Get the current window size * Get the current window size
* @remarks * @remarks
@ -4427,7 +4427,7 @@ interface UserInterface {
* RAM cost: 0.2 GB * RAM cost: 0.2 GB
*/ */
clearTerminal(): void; clearTerminal(): void;
} };
/** /**
* Collection of all functions passed to scripts * 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} * {@link https://bitburner.readthedocs.io/en/latest/netscript/netscriptjs.html| ns2 in-game docs}
* <hr> * <hr>
*/ */
export interface NS { export type NS = {
/** /**
* Namespace for hacknet functions. * Namespace for hacknet functions.
* @remarks RAM cost: 4 GB * @remarks RAM cost: 4 GB
@ -6853,7 +6853,7 @@ export interface NS {
getSharePower(): number; getSharePower(): number;
enums: NSEnums; enums: NSEnums;
} };
// BASE ENUMS // BASE ENUMS
/** @public */ /** @public */
@ -7073,7 +7073,7 @@ export type NSEnums = {
* @public * @public
*/ */
export interface OfficeAPI { export type OfficeAPI = {
/** /**
* Hire an employee. * Hire an employee.
* @param divisionName - Name of the division * @param divisionName - Name of the division
@ -7165,7 +7165,7 @@ export interface OfficeAPI {
* @returns Cost of upgrading the office * @returns Cost of upgrading the office
*/ */
getOfficeSizeUpgradeCost(divisionName: string, city: CityName | `${CityName}`, asize: number): number; getOfficeSizeUpgradeCost(divisionName: string, city: CityName | `${CityName}`, asize: number): number;
} };
/** /**
* Corporation Warehouse API * Corporation Warehouse API
@ -7173,7 +7173,7 @@ export interface OfficeAPI {
* Requires the Warehouse API upgrade from your corporation. * Requires the Warehouse API upgrade from your corporation.
* @public * @public
*/ */
export interface WarehouseAPI { export type WarehouseAPI = {
/** /**
* Set material sell data. * Set material sell data.
* @param divisionName - Name of the division * @param divisionName - Name of the division
@ -7396,13 +7396,13 @@ export interface WarehouseAPI {
* @returns true if warehouse is present, false if not * @returns true if warehouse is present, false if not
*/ */
hasWarehouse(divisionName: string, city: CityName | `${CityName}`): boolean; hasWarehouse(divisionName: string, city: CityName | `${CityName}`): boolean;
} };
/** /**
* Corporation API * Corporation API
* @public * @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. Does not require API access.
* @returns whether the player has a corporation */ * @returns whether the player has a corporation */
hasCorporation(): boolean; hasCorporation(): boolean;
@ -7513,7 +7513,8 @@ export interface Corporation extends WarehouseAPI, OfficeAPI {
* Bonus time makes the game progress faster. * Bonus time makes the game progress faster.
* @returns Bonus time for the Corporation mechanic in milliseconds. */ * @returns Bonus time for the Corporation mechanic in milliseconds. */
getBonusTime(): number; getBonusTime(): number;
} } & WarehouseAPI &
OfficeAPI;
/** Product rating information /** Product rating information
* @public */ * @public */

@ -7,7 +7,7 @@ import { Script } from "../../../src/Script/Script";
import { WorkerScript } from "../../../src/Netscript/WorkerScript"; import { WorkerScript } from "../../../src/Netscript/WorkerScript";
import { calculateRamUsage } from "../../../src/Script/RamCalculations"; import { calculateRamUsage } from "../../../src/Script/RamCalculations";
import { ns } from "../../../src/NetscriptFunctions"; import { ns } from "../../../src/NetscriptFunctions";
import { ExternalAPI, InternalAPI } from "src/Netscript/APIWrapper"; import { InternalAPI, NetscriptContext } from "src/Netscript/APIWrapper";
import { Singularity } from "@nsdefs"; import { Singularity } from "@nsdefs";
type PotentiallyAsyncFunction = (arg?: unknown) => { catch?: PotentiallyAsyncFunction }; type PotentiallyAsyncFunction = (arg?: unknown) => { catch?: PotentiallyAsyncFunction };
@ -20,15 +20,10 @@ function getFunction(fn: unknown) {
function grabCost<API>(ramEntry: RamCostTree<API>[keyof API]) { function grabCost<API>(ramEntry: RamCostTree<API>[keyof API]) {
if (typeof ramEntry === "function") return ramEntry(); if (typeof ramEntry === "function") return ramEntry();
if (typeof ramEntry === "number") return ramEntry; if (typeof ramEntry === "number") return ramEntry;
throw new Error("Invalid ramcost"); throw new Error("Invalid ramcost: " + ramEntry);
} }
function isRemovedFunction(fn: Function) { function isRemovedFunction(ctx: NetscriptContext, fn: (ctx: NetscriptContext) => (...__: unknown[]) => unknown) {
try { return /REMOVED FUNCTION/.test(fn(ctx) + "");
fn();
} catch {
return true;
}
return false;
} }
describe("Netscript RAM Calculation/Generation Tests", function () { describe("Netscript RAM Calculation/Generation Tests", function () {
@ -112,7 +107,7 @@ describe("Netscript RAM Calculation/Generation Tests", function () {
describe("ns", () => { describe("ns", () => {
function testLayer<API>( function testLayer<API>(
internalLayer: InternalAPI<API>, internalLayer: InternalAPI<API>,
externalLayer: ExternalAPI<API>, externalLayer: API,
ramLayer: RamCostTree<API>, ramLayer: RamCostTree<API>,
path: string[], path: string[],
extraLayerCost: number, extraLayerCost: number,
@ -122,18 +117,22 @@ describe("Netscript RAM Calculation/Generation Tests", function () {
const newPath = [...path, key as string]; const newPath = [...path, key as string];
if (typeof val === "function") { if (typeof val === "function") {
// Removed functions have no ram cost and should be skipped. // 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 fn = getFunction(externalLayer[key]);
const fnName = newPath.join("."); const fnName = newPath.join(".");
if (!(key in ramLayer)) {
throw new Error("Missing ramcost for " + fnName);
}
const expectedRam = grabCost(ramLayer[key]); const expectedRam = grabCost(ramLayer[key]);
it(`${fnName}()`, () => combinedRamCheck(fn, newPath, expectedRam, extraLayerCost)); 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. //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. //hacknet is currently the only layer with a layer cost.
const layerCost = key === "hacknet" ? 4 : 0; const layerCost = key === "hacknet" ? 4 : 0;
testLayer(val as InternalAPI<unknown>, externalLayer[key], ramLayer[key], newPath, layerCost); testLayer(val as InternalAPI<unknown>, 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 = ( const singObjects = (
Object.entries(ns.singularity) as [keyof Singularity, InternalAPI<Singularity>[keyof Singularity]][] Object.entries(ns.singularity) as [keyof Singularity, InternalAPI<Singularity>[keyof Singularity]][]
) )
.filter(([_, v]) => typeof v === "function" && !isRemovedFunction(v)) .filter(([__, v]) => typeof v === "function" && !isRemovedFunction({ workerScript }, v))
.map(([name]) => { .map(([name]) => {
return { return {
name, name,