mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2024-12-18 12:15:44 +01:00
BUGFIX: Fix additionalMsec overflow issue (#941)
This commit is contained in:
parent
21c7f56d23
commit
61ffed9b3a
@ -7,7 +7,7 @@ import { Player } from "@player";
|
||||
import { ScriptDeath } from "./ScriptDeath";
|
||||
import { formatExp, formatMoney, formatRam, formatThreads } from "../ui/formatNumber";
|
||||
import { ScriptArg } from "./ScriptArg";
|
||||
import { BasicHGWOptions, RunningScript as IRunningScript, Person as IPerson, Server as IServer } from "@nsdefs";
|
||||
import { RunningScript as IRunningScript, Person as IPerson, Server as IServer } from "@nsdefs";
|
||||
import { Server } from "../Server/Server";
|
||||
import {
|
||||
calculateHackingChance,
|
||||
@ -49,7 +49,7 @@ export const helpers = {
|
||||
argsToString,
|
||||
makeBasicErrorMsg,
|
||||
makeRuntimeErrorMsg,
|
||||
resolveNetscriptRequestedThreads,
|
||||
validateHGWOptions,
|
||||
checkEnvFlags,
|
||||
checkSingularityAccess,
|
||||
netscriptDelay,
|
||||
@ -84,46 +84,18 @@ export interface CompleteRunOptions {
|
||||
export interface CompleteSpawnOptions extends CompleteRunOptions {
|
||||
spawnDelay: PositiveInteger;
|
||||
}
|
||||
/** HGWOptions with non-optional, type-validated members, for passing between internal functions. */
|
||||
export interface CompleteHGWOptions {
|
||||
threads: PositiveInteger;
|
||||
stock: boolean;
|
||||
additionalMsec: number;
|
||||
}
|
||||
|
||||
export function assertString(ctx: NetscriptContext, argName: string, v: unknown): asserts v is string {
|
||||
if (typeof v !== "string")
|
||||
throw makeRuntimeErrorMsg(ctx, `${argName} expected to be a string. ${debugType(v)}`, "TYPE");
|
||||
}
|
||||
|
||||
/** Will probably remove the below function in favor of a different approach to object type assertion.
|
||||
* This method cannot be used to handle optional properties. */
|
||||
export function assertObjectType<T extends object>(
|
||||
ctx: NetscriptContext,
|
||||
name: string,
|
||||
obj: unknown,
|
||||
desiredObject: T,
|
||||
): asserts obj is T {
|
||||
if (typeof obj !== "object" || obj === null) {
|
||||
throw makeRuntimeErrorMsg(
|
||||
ctx,
|
||||
`Type ${obj === null ? "null" : typeof obj} provided for ${name}. Must be an object.`,
|
||||
"TYPE",
|
||||
);
|
||||
}
|
||||
for (const [key, val] of Object.entries(desiredObject)) {
|
||||
if (!Object.hasOwn(obj, key)) {
|
||||
throw makeRuntimeErrorMsg(
|
||||
ctx,
|
||||
`Object provided for argument ${name} is missing required property ${key}.`,
|
||||
"TYPE",
|
||||
);
|
||||
}
|
||||
const objVal = (obj as Record<string, unknown>)[key];
|
||||
if (typeof val !== typeof objVal) {
|
||||
throw makeRuntimeErrorMsg(
|
||||
ctx,
|
||||
`Incorrect type ${typeof objVal} provided for property ${key} on ${name} argument. Should be type ${typeof val}.`,
|
||||
"TYPE",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const userFriendlyString = (v: unknown): string => {
|
||||
const clip = (s: string): string => {
|
||||
if (s.length > 15) return s.slice(0, 12) + "...";
|
||||
@ -266,7 +238,7 @@ function makeBasicErrorMsg(ws: WorkerScript | ScriptDeath, msg: string, type = "
|
||||
}
|
||||
|
||||
/** Creates an error message string with a stack trace. */
|
||||
function makeRuntimeErrorMsg(ctx: NetscriptContext, msg: string, type = "RUNTIME"): string {
|
||||
export function makeRuntimeErrorMsg(ctx: NetscriptContext, msg: string, type = "RUNTIME"): string {
|
||||
const errstack = new Error().stack;
|
||||
if (errstack === undefined) throw new Error("how did we not throw an error?");
|
||||
const stack = errstack.split("\n").slice(1);
|
||||
@ -322,23 +294,44 @@ function makeRuntimeErrorMsg(ctx: NetscriptContext, msg: string, type = "RUNTIME
|
||||
}
|
||||
}
|
||||
|
||||
/** Validate requested number of threads for h/g/w options */
|
||||
function resolveNetscriptRequestedThreads(ctx: NetscriptContext, requestedThreads?: number): number {
|
||||
function validateHGWOptions(ctx: NetscriptContext, opts: unknown): CompleteHGWOptions {
|
||||
const result: CompleteHGWOptions = {
|
||||
threads: 1 as PositiveInteger,
|
||||
stock: false,
|
||||
additionalMsec: 0,
|
||||
};
|
||||
if (opts == null) {
|
||||
return result;
|
||||
}
|
||||
if (typeof opts !== "object") {
|
||||
throw makeRuntimeErrorMsg(ctx, `BasicHGWOptions must be an object if specified, was ${opts}`);
|
||||
}
|
||||
// Safe assertion since threadOrOption type has been narrowed to a non-null object
|
||||
const options = opts as Unknownify<CompleteHGWOptions>;
|
||||
result.stock = !!options.stock;
|
||||
result.additionalMsec = number(ctx, "opts.additionalMsec", options.additionalMsec ?? 0);
|
||||
if (result.additionalMsec < 0) {
|
||||
throw makeRuntimeErrorMsg(ctx, `additionalMsec must be non-negative, got ${options.additionalMsec}`);
|
||||
}
|
||||
if (result.additionalMsec > 1e9) {
|
||||
throw makeRuntimeErrorMsg(ctx, `additionalMsec too large (>1e9), got ${options.additionalMsec}`);
|
||||
}
|
||||
const requestedThreads = options.threads;
|
||||
const threads = ctx.workerScript.scriptRef.threads;
|
||||
if (!requestedThreads) {
|
||||
return isNaN(threads) || threads < 1 ? 1 : threads;
|
||||
result.threads = (isNaN(threads) || threads < 1 ? 1 : threads) as PositiveInteger;
|
||||
} else {
|
||||
const positiveThreads = positiveInteger(ctx, "opts.threads", requestedThreads);
|
||||
if (positiveThreads > threads) {
|
||||
throw makeRuntimeErrorMsg(
|
||||
ctx,
|
||||
`Too many threads requested by ${ctx.function}. Requested: ${positiveThreads}. Has: ${threads}.`,
|
||||
);
|
||||
}
|
||||
result.threads = positiveThreads;
|
||||
}
|
||||
const requestedThreadsAsInt = requestedThreads | 0;
|
||||
if (isNaN(requestedThreads) || requestedThreadsAsInt < 1) {
|
||||
throw makeRuntimeErrorMsg(ctx, `Invalid thread count: ${requestedThreads}. Threads must be a positive number.`);
|
||||
}
|
||||
if (requestedThreadsAsInt > threads) {
|
||||
throw makeRuntimeErrorMsg(
|
||||
ctx,
|
||||
`Too many threads requested by ${ctx.function}. Requested: ${requestedThreads}. Has: ${threads}.`,
|
||||
);
|
||||
}
|
||||
return requestedThreadsAsInt;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Validate singularity access by throwing an error if the player does not have access. */
|
||||
@ -472,18 +465,9 @@ function isScriptArgs(args: unknown): args is ScriptArg[] {
|
||||
return Array.isArray(args) && args.every(isScriptArg);
|
||||
}
|
||||
|
||||
function hack(
|
||||
ctx: NetscriptContext,
|
||||
hostname: string,
|
||||
manual: boolean,
|
||||
{ threads: requestedThreads, stock, additionalMsec: requestedSec }: BasicHGWOptions = {},
|
||||
): Promise<number> {
|
||||
function hack(ctx: NetscriptContext, hostname: string, manual: boolean, opts: unknown): Promise<number> {
|
||||
const ws = ctx.workerScript;
|
||||
const threads = helpers.resolveNetscriptRequestedThreads(ctx, requestedThreads);
|
||||
const additionalMsec = number(ctx, "opts.additionalMsec", requestedSec ?? 0);
|
||||
if (additionalMsec < 0) {
|
||||
throw makeRuntimeErrorMsg(ctx, `additionalMsec must be non-negative, got ${additionalMsec}`);
|
||||
}
|
||||
const { threads, stock, additionalMsec } = validateHGWOptions(ctx, opts);
|
||||
const server = getServer(ctx, hostname);
|
||||
if (!(server instanceof Server)) {
|
||||
throw makeRuntimeErrorMsg(ctx, "Cannot be executed on this server.");
|
||||
|
@ -51,7 +51,7 @@ import { runScriptFromScript } from "./NetscriptWorker";
|
||||
import { killWorkerScript, killWorkerScriptByPid } from "./Netscript/killWorkerScript";
|
||||
import { workerScripts } from "./Netscript/WorkerScripts";
|
||||
import { WorkerScript } from "./Netscript/WorkerScript";
|
||||
import { helpers, assertObjectType, wrapUserNode } from "./Netscript/NetscriptHelpers";
|
||||
import { helpers, wrapUserNode } from "./Netscript/NetscriptHelpers";
|
||||
import {
|
||||
formatExp,
|
||||
formatNumberNoSuffix,
|
||||
@ -78,7 +78,7 @@ import { NetscriptCorporation } from "./NetscriptFunctions/Corporation";
|
||||
import { NetscriptFormulas } from "./NetscriptFunctions/Formulas";
|
||||
import { NetscriptStockMarket } from "./NetscriptFunctions/StockMarket";
|
||||
import { NetscriptGrafting } from "./NetscriptFunctions/Grafting";
|
||||
import { NS, RecentScript, BasicHGWOptions, ProcessInfo, NSEnums } from "@nsdefs";
|
||||
import { NS, RecentScript, ProcessInfo, NSEnums } from "@nsdefs";
|
||||
import { NetscriptSingularity } from "./NetscriptFunctions/Singularity";
|
||||
|
||||
import { dialogBoxCreate } from "./ui/React/DialogBox";
|
||||
@ -154,15 +154,10 @@ export const ns: InternalAPI<NSFull> = {
|
||||
return out;
|
||||
},
|
||||
hasTorRouter: () => () => Player.hasTorRouter(),
|
||||
hack:
|
||||
(ctx) =>
|
||||
(_hostname, opts = {}) => {
|
||||
const hostname = helpers.string(ctx, "hostname", _hostname);
|
||||
// TODO 2.2: better type safety rework for functions using assertObjectType, then remove function.
|
||||
const optsValidator: BasicHGWOptions = {};
|
||||
assertObjectType(ctx, "opts", opts, optsValidator);
|
||||
return helpers.hack(ctx, hostname, false, opts);
|
||||
},
|
||||
hack: (ctx) => (_hostname, opts?) => {
|
||||
const hostname = helpers.string(ctx, "hostname", _hostname);
|
||||
return helpers.hack(ctx, hostname, false, opts);
|
||||
},
|
||||
hackAnalyzeThreads: (ctx) => (_hostname, _hackAmount) => {
|
||||
const hostname = helpers.string(ctx, "hostname", _hostname);
|
||||
const hackAmount = helpers.number(ctx, "hackAmount", _hackAmount);
|
||||
@ -252,66 +247,58 @@ export const ns: InternalAPI<NSFull> = {
|
||||
helpers.log(ctx, () => `Sleeping for ${convertTimeMsToTimeElapsedString(time, true)}.`);
|
||||
return new Promise((resolve) => setTimeout(() => resolve(true), time));
|
||||
},
|
||||
grow:
|
||||
(ctx) =>
|
||||
(_hostname, opts = {}) => {
|
||||
const hostname = helpers.string(ctx, "hostname", _hostname);
|
||||
const optsValidator: BasicHGWOptions = {};
|
||||
assertObjectType(ctx, "opts", opts, optsValidator);
|
||||
const threads = helpers.resolveNetscriptRequestedThreads(ctx, opts.threads);
|
||||
const additionalMsec = helpers.number(ctx, "opts.additionalMsec", opts.additionalMsec ?? 0);
|
||||
if (additionalMsec < 0) {
|
||||
throw helpers.makeRuntimeErrorMsg(ctx, `additionalMsec must be non-negative, got ${additionalMsec}`);
|
||||
}
|
||||
grow: (ctx) => (_hostname, opts?) => {
|
||||
const hostname = helpers.string(ctx, "hostname", _hostname);
|
||||
const { threads, stock, additionalMsec } = helpers.validateHGWOptions(ctx, opts);
|
||||
|
||||
const server = helpers.getServer(ctx, hostname);
|
||||
if (!(server instanceof Server)) {
|
||||
helpers.log(ctx, () => "Cannot be executed on this server.");
|
||||
return Promise.resolve(0);
|
||||
}
|
||||
const server = helpers.getServer(ctx, hostname);
|
||||
if (!(server instanceof Server)) {
|
||||
helpers.log(ctx, () => "Cannot be executed on this server.");
|
||||
return Promise.resolve(0);
|
||||
}
|
||||
|
||||
const host = GetServer(ctx.workerScript.hostname);
|
||||
if (host === null) {
|
||||
throw new Error("Workerscript host is null");
|
||||
}
|
||||
const host = GetServer(ctx.workerScript.hostname);
|
||||
if (host === null) {
|
||||
throw new Error("Workerscript host is null");
|
||||
}
|
||||
|
||||
// No root access or skill level too low
|
||||
const canHack = netscriptCanGrow(server);
|
||||
if (!canHack.res) {
|
||||
throw helpers.makeRuntimeErrorMsg(ctx, canHack.msg || "");
|
||||
}
|
||||
// No root access or skill level too low
|
||||
const canHack = netscriptCanGrow(server);
|
||||
if (!canHack.res) {
|
||||
throw helpers.makeRuntimeErrorMsg(ctx, canHack.msg || "");
|
||||
}
|
||||
|
||||
const growTime = calculateGrowTime(server, Player) + additionalMsec / 1000.0;
|
||||
const growTime = calculateGrowTime(server, Player) + additionalMsec / 1000.0;
|
||||
helpers.log(
|
||||
ctx,
|
||||
() =>
|
||||
`Executing on '${server.hostname}' in ${convertTimeMsToTimeElapsedString(
|
||||
growTime * 1000,
|
||||
true,
|
||||
)} (t=${formatThreads(threads)}).`,
|
||||
);
|
||||
return helpers.netscriptDelay(ctx, growTime * 1000).then(function () {
|
||||
const moneyBefore = server.moneyAvailable <= 0 ? 1 : server.moneyAvailable;
|
||||
processSingleServerGrowth(server, threads, host.cpuCores);
|
||||
const moneyAfter = server.moneyAvailable;
|
||||
ctx.workerScript.scriptRef.recordGrow(server.hostname, threads);
|
||||
const expGain = calculateHackingExpGain(server, Player) * threads;
|
||||
const logGrowPercent = moneyAfter / moneyBefore - 1;
|
||||
helpers.log(
|
||||
ctx,
|
||||
() =>
|
||||
`Executing on '${server.hostname}' in ${convertTimeMsToTimeElapsedString(
|
||||
growTime * 1000,
|
||||
true,
|
||||
)} (t=${formatThreads(threads)}).`,
|
||||
`Available money on '${server.hostname}' grown by ${formatPercent(logGrowPercent, 6)}. Gained ${formatExp(
|
||||
expGain,
|
||||
)} hacking exp (t=${formatThreads(threads)}).`,
|
||||
);
|
||||
return helpers.netscriptDelay(ctx, growTime * 1000).then(function () {
|
||||
const moneyBefore = server.moneyAvailable <= 0 ? 1 : server.moneyAvailable;
|
||||
processSingleServerGrowth(server, threads, host.cpuCores);
|
||||
const moneyAfter = server.moneyAvailable;
|
||||
ctx.workerScript.scriptRef.recordGrow(server.hostname, threads);
|
||||
const expGain = calculateHackingExpGain(server, Player) * threads;
|
||||
const logGrowPercent = moneyAfter / moneyBefore - 1;
|
||||
helpers.log(
|
||||
ctx,
|
||||
() =>
|
||||
`Available money on '${server.hostname}' grown by ${formatPercent(logGrowPercent, 6)}. Gained ${formatExp(
|
||||
expGain,
|
||||
)} hacking exp (t=${formatThreads(threads)}).`,
|
||||
);
|
||||
ctx.workerScript.scriptRef.onlineExpGained += expGain;
|
||||
Player.gainHackingExp(expGain);
|
||||
if (opts.stock) {
|
||||
influenceStockThroughServerGrow(server, moneyAfter - moneyBefore);
|
||||
}
|
||||
return Promise.resolve(moneyAfter / moneyBefore);
|
||||
});
|
||||
},
|
||||
ctx.workerScript.scriptRef.onlineExpGained += expGain;
|
||||
Player.gainHackingExp(expGain);
|
||||
if (stock) {
|
||||
influenceStockThroughServerGrow(server, moneyAfter - moneyBefore);
|
||||
}
|
||||
return Promise.resolve(moneyAfter / moneyBefore);
|
||||
});
|
||||
},
|
||||
growthAnalyze:
|
||||
(ctx) =>
|
||||
(_host, _multiplier, _cores = 1) => {
|
||||
@ -359,64 +346,56 @@ export const ns: InternalAPI<NSFull> = {
|
||||
|
||||
return 2 * CONSTANTS.ServerFortifyAmount * threads;
|
||||
},
|
||||
weaken:
|
||||
(ctx) =>
|
||||
async (_hostname, opts = {}) => {
|
||||
const hostname = helpers.string(ctx, "hostname", _hostname);
|
||||
const optsValidator: BasicHGWOptions = {};
|
||||
assertObjectType(ctx, "opts", opts, optsValidator);
|
||||
const threads = helpers.resolveNetscriptRequestedThreads(ctx, opts.threads);
|
||||
const additionalMsec = helpers.number(ctx, "opts.additionalMsec", opts.additionalMsec ?? 0);
|
||||
if (additionalMsec < 0) {
|
||||
throw helpers.makeRuntimeErrorMsg(ctx, `additionalMsec must be non-negative, got ${additionalMsec}`);
|
||||
}
|
||||
weaken: (ctx) => async (_hostname, opts?) => {
|
||||
const hostname = helpers.string(ctx, "hostname", _hostname);
|
||||
const { threads, additionalMsec } = helpers.validateHGWOptions(ctx, opts);
|
||||
|
||||
const server = helpers.getServer(ctx, hostname);
|
||||
if (!(server instanceof Server)) {
|
||||
helpers.log(ctx, () => "Cannot be executed on this server.");
|
||||
const server = helpers.getServer(ctx, hostname);
|
||||
if (!(server instanceof Server)) {
|
||||
helpers.log(ctx, () => "Cannot be executed on this server.");
|
||||
return Promise.resolve(0);
|
||||
}
|
||||
|
||||
// No root access or skill level too low
|
||||
const canHack = netscriptCanWeaken(server);
|
||||
if (!canHack.res) {
|
||||
throw helpers.makeRuntimeErrorMsg(ctx, canHack.msg || "");
|
||||
}
|
||||
|
||||
const weakenTime = calculateWeakenTime(server, Player) + additionalMsec / 1000.0;
|
||||
helpers.log(
|
||||
ctx,
|
||||
() =>
|
||||
`Executing on '${server.hostname}' in ${convertTimeMsToTimeElapsedString(
|
||||
weakenTime * 1000,
|
||||
true,
|
||||
)} (t=${formatThreads(threads)})`,
|
||||
);
|
||||
return helpers.netscriptDelay(ctx, weakenTime * 1000).then(function () {
|
||||
const host = GetServer(ctx.workerScript.hostname);
|
||||
if (host === null) {
|
||||
helpers.log(ctx, () => "Server is null, did it die?");
|
||||
return Promise.resolve(0);
|
||||
}
|
||||
|
||||
// No root access or skill level too low
|
||||
const canHack = netscriptCanWeaken(server);
|
||||
if (!canHack.res) {
|
||||
throw helpers.makeRuntimeErrorMsg(ctx, canHack.msg || "");
|
||||
}
|
||||
|
||||
const weakenTime = calculateWeakenTime(server, Player) + additionalMsec / 1000.0;
|
||||
const cores = host.cpuCores;
|
||||
const coreBonus = getCoreBonus(cores);
|
||||
const weakenAmt = CONSTANTS.ServerWeakenAmount * threads * coreBonus;
|
||||
server.weaken(weakenAmt);
|
||||
ctx.workerScript.scriptRef.recordWeaken(server.hostname, threads);
|
||||
const expGain = calculateHackingExpGain(server, Player) * threads;
|
||||
helpers.log(
|
||||
ctx,
|
||||
() =>
|
||||
`Executing on '${server.hostname}' in ${convertTimeMsToTimeElapsedString(
|
||||
weakenTime * 1000,
|
||||
true,
|
||||
)} (t=${formatThreads(threads)})`,
|
||||
`'${server.hostname}' security level weakened to ${server.hackDifficulty}. Gained ${formatExp(
|
||||
expGain,
|
||||
)} hacking exp (t=${formatThreads(threads)})`,
|
||||
);
|
||||
return helpers.netscriptDelay(ctx, weakenTime * 1000).then(function () {
|
||||
const host = GetServer(ctx.workerScript.hostname);
|
||||
if (host === null) {
|
||||
helpers.log(ctx, () => "Server is null, did it die?");
|
||||
return Promise.resolve(0);
|
||||
}
|
||||
const cores = helpers.getServer(ctx, ctx.workerScript.hostname).cpuCores;
|
||||
const coreBonus = getCoreBonus(cores);
|
||||
const weakenAmt = CONSTANTS.ServerWeakenAmount * threads * coreBonus;
|
||||
server.weaken(weakenAmt);
|
||||
ctx.workerScript.scriptRef.recordWeaken(server.hostname, threads);
|
||||
const expGain = calculateHackingExpGain(server, Player) * threads;
|
||||
helpers.log(
|
||||
ctx,
|
||||
() =>
|
||||
`'${server.hostname}' security level weakened to ${server.hackDifficulty}. Gained ${formatExp(
|
||||
expGain,
|
||||
)} hacking exp (t=${formatThreads(threads)})`,
|
||||
);
|
||||
ctx.workerScript.scriptRef.onlineExpGained += expGain;
|
||||
Player.gainHackingExp(expGain);
|
||||
// Account for hidden multiplier in Server.weaken()
|
||||
return Promise.resolve(weakenAmt * currentNodeMults.ServerWeakenRate);
|
||||
});
|
||||
},
|
||||
ctx.workerScript.scriptRef.onlineExpGained += expGain;
|
||||
Player.gainHackingExp(expGain);
|
||||
// Account for hidden multiplier in Server.weaken()
|
||||
return Promise.resolve(weakenAmt * currentNodeMults.ServerWeakenRate);
|
||||
});
|
||||
},
|
||||
weakenAnalyze:
|
||||
(ctx) =>
|
||||
(_threads, _cores = 1) => {
|
||||
|
@ -530,7 +530,7 @@ export function NetscriptSingularity(): InternalAPI<ISingularity> {
|
||||
manualHack: (ctx) => () => {
|
||||
helpers.checkSingularityAccess(ctx);
|
||||
const server = Player.getCurrentServer();
|
||||
return helpers.hack(ctx, server.hostname, true);
|
||||
return helpers.hack(ctx, server.hostname, true, null);
|
||||
},
|
||||
installBackdoor: (ctx) => async (): Promise<void> => {
|
||||
helpers.checkSingularityAccess(ctx);
|
||||
|
@ -5,9 +5,43 @@ import { defaultTheme } from "../Themes/Themes";
|
||||
import { defaultStyles } from "../Themes/Styles";
|
||||
import { CONSTANTS } from "../Constants";
|
||||
import { hash } from "../hash/hash";
|
||||
import { InternalAPI } from "../Netscript/APIWrapper";
|
||||
import { InternalAPI, NetscriptContext } from "../Netscript/APIWrapper";
|
||||
import { Terminal } from "../../src/Terminal";
|
||||
import { helpers, assertObjectType } from "../Netscript/NetscriptHelpers";
|
||||
import { helpers, makeRuntimeErrorMsg } from "../Netscript/NetscriptHelpers";
|
||||
|
||||
/** Will probably remove the below function in favor of a different approach to object type assertion.
|
||||
* This method cannot be used to handle optional properties. */
|
||||
export function assertObjectType<T extends object>(
|
||||
ctx: NetscriptContext,
|
||||
name: string,
|
||||
obj: unknown,
|
||||
desiredObject: T,
|
||||
): asserts obj is T {
|
||||
if (typeof obj !== "object" || obj === null) {
|
||||
throw makeRuntimeErrorMsg(
|
||||
ctx,
|
||||
`Type ${obj === null ? "null" : typeof obj} provided for ${name}. Must be an object.`,
|
||||
"TYPE",
|
||||
);
|
||||
}
|
||||
for (const [key, val] of Object.entries(desiredObject)) {
|
||||
if (!Object.hasOwn(obj, key)) {
|
||||
throw makeRuntimeErrorMsg(
|
||||
ctx,
|
||||
`Object provided for argument ${name} is missing required property ${key}.`,
|
||||
"TYPE",
|
||||
);
|
||||
}
|
||||
const objVal = (obj as Record<string, unknown>)[key];
|
||||
if (typeof val !== typeof objVal) {
|
||||
throw makeRuntimeErrorMsg(
|
||||
ctx,
|
||||
`Incorrect type ${typeof objVal} provided for property ${key} on ${name} argument. Should be type ${typeof val}.`,
|
||||
"TYPE",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function NetscriptUserInterface(): InternalAPI<IUserInterface> {
|
||||
return {
|
||||
|
Loading…
Reference in New Issue
Block a user