Unify error handling

This commit is contained in:
Snarling 2022-08-28 05:33:38 -04:00
parent 8dd507883a
commit 5798c4c7d3
21 changed files with 149 additions and 324 deletions

@ -126,15 +126,15 @@ function installAugmentations(force?: boolean): boolean {
if (ownedAug.name === AugmentationNames.NeuroFluxGovernor) {
level = ` - ${ownedAug.level}`;
}
augmentationList += aug.name + level + "<br>";
augmentationList += aug.name + level + "\n";
}
Player.queuedAugmentations = [];
if (!force) {
dialogBoxCreate(
"You slowly drift to sleep as scientists put you under in order " +
"to install the following Augmentations:<br>" +
"to install the following Augmentations:\n" +
augmentationList +
"<br>You wake up in your home...you feel different...",
"\nYou wake up in your home...you feel different...",
);
}
prestigeAugmentation();

@ -11,7 +11,7 @@ export function win(p: IPlayer, n: number): void {
export function reachedLimit(p: IPlayer): boolean {
const reached = p.getCasinoWinnings() > gainLimit;
if (reached) {
dialogBoxCreate(<>Alright cheater get out of here. You're not allowed here anymore.</>);
dialogBoxCreate("Alright cheater get out of here. You're not allowed here anymore.");
}
return reached;
}
@ -24,7 +24,7 @@ export class Game<T, U> extends React.Component<T, U> {
reachedLimit(p: IPlayer): boolean {
const reached = p.getCasinoWinnings() > gainLimit;
if (reached) {
dialogBoxCreate(<>Alright cheater get out of here. You're not allowed here anymore.</>);
dialogBoxCreate("Alright cheater get out of here. You're not allowed here anymore.");
}
return reached;
}

@ -120,7 +120,7 @@ export class Corporation {
if (isNaN(this.funds) || this.funds === Infinity || this.funds === -Infinity) {
dialogBoxCreate(
"There was an error calculating your Corporations funds and they got reset to 0. " +
"This is a bug. Please report to game developer.<br><br>" +
"This is a bug. Please report to game developer.\n\n" +
"(Your funds have been set to $150b for the inconvenience)",
);
this.funds = 150e9;

@ -825,14 +825,7 @@ export class Industry implements IIndustry {
sellAmt = eval(tmp);
} catch (e) {
dialogBoxCreate(
"Error evaluating your sell amount for material " +
mat.name +
" in " +
this.name +
"'s " +
city +
" office. The sell amount " +
"is being set to zero",
`Error evaluating your sell amount for material ${mat.name} in ${this.name}'s ${city} office. The sell amount is being set to zero`,
);
sellAmt = 0;
}
@ -879,27 +872,13 @@ export class Industry implements IIndustry {
amt = eval(amtStr);
} catch (e) {
dialogBoxCreate(
"Calculating export for " +
mat.name +
" in " +
this.name +
"'s " +
city +
" division failed with " +
"error: " +
e,
`Calculating export for ${mat.name} in ${this.name}'s ${city} division failed with error: ${e}`,
);
continue;
}
if (isNaN(amt)) {
dialogBoxCreate(
"Error calculating export amount for " +
mat.name +
" in " +
this.name +
"'s " +
city +
" division.",
`Error calculating export amount for ${mat.name} in ${this.name}'s ${city} division.`,
);
continue;
}
@ -1172,13 +1151,7 @@ export class Industry implements IIndustry {
tmp = eval(tmp);
} catch (e) {
dialogBoxCreate(
"Error evaluating your sell price expression for " +
product.name +
" in " +
this.name +
"'s " +
city +
" office. Sell price is being set to MAX",
`Error evaluating your sell price expression for ${product.name} in ${this.name}'s ${city} office. Sell price is being set to MAX`,
);
tmp = product.maxsll;
}

@ -60,9 +60,7 @@ export function BribeFactionModal(props: IProps): React.ReactElement {
const fac = Factions[selectedFaction];
if (disabled) return;
const rep = repGain(money);
dialogBoxCreate(
"You gained " + numeralWrapper.formatReputation(rep) + " reputation with " + fac.name + " by bribing them.",
);
dialogBoxCreate(`You gained ${numeralWrapper.formatReputation(rep)} reputation with ${fac.name} by bribing them.`);
fac.playerReputation += rep;
corp.funds = corp.funds - money;
props.onClose();

@ -89,14 +89,11 @@ export function IssueNewSharesModal(props: IProps): React.ReactElement {
let dialogContents =
`Issued ${numeralWrapper.format(newShares, "0.000a")} new shares` +
` and raised ${numeralWrapper.formatMoney(profit)}.`;
if (privateShares > 0) {
dialogContents += `<br>${numeralWrapper.format(
privateShares,
"0.000a",
)} of these shares were bought by private investors.`;
}
dialogContents += `<br><br>Stock price decreased to ${numeralWrapper.formatMoney(corp.sharePrice)}`;
` and raised ${numeralWrapper.formatMoney(profit)}.` +
(privateShares > 0)
? "\n" + numeralWrapper.format(privateShares, "0.000a") + "of these shares were bought by private investors."
: "";
dialogContents += `\n\nStock price decreased to ${numeralWrapper.formatMoney(corp.sharePrice)}`;
dialogBoxCreate(dialogContents);
}

@ -42,9 +42,7 @@ function Upgrade({ n, division }: INodeProps): React.ReactElement {
}
dialogBoxCreate(
`Researched ${n.text}. It may take a market cycle ` +
`(~${CorporationConstants.SecsPerMarketCycle} seconds) before the effects of ` +
`the Research apply.`,
`Researched ${n.text}. It may take a market cycle (~${CorporationConstants.SecsPerMarketCycle} seconds) before the effects of the Research apply.`,
);
}

@ -41,8 +41,7 @@ export function ThrowPartyModal(props: IProps): React.ReactElement {
if (mult > 0) {
dialogBoxCreate(
"You threw a party for the office! The morale and happiness " +
"of each employee increased by " +
"You threw a party for the office! The morale and happiness of each employee increased by " +
numeralWrapper.formatPercentage(mult - 1),
);
}

@ -94,12 +94,9 @@ export function purchaseAugmentation(aug: Augmentation, fac: Faction, sing = fal
return "You purchased " + aug.name;
} else if (!Settings.SuppressBuyAugmentationConfirmation) {
dialogBoxCreate(
"You purchased " +
aug.name +
". Its enhancements will not take " +
"effect until they are installed. To install your augmentations, go to the " +
"'Augmentations' tab on the left-hand navigation menu. Purchasing additional " +
"augmentations will now be more expensive.",
`You purchased ${aug.name}. Its enhancements will not take effect until they are installed.` +
"To install your augmentations, go to the 'Augmentations' tab on the left-hand navigation menu." +
"Purchasing additional augmentations will now be more expensive.",
);
}
} else {

@ -33,8 +33,8 @@ export function purchaseTorRouter(p: IPlayer): void {
p.getHomeComputer().serversOnNetwork.push(darkweb.hostname);
darkweb.serversOnNetwork.push(p.getHomeComputer().hostname);
dialogBoxCreate(
"You have purchased a TOR router!<br>" +
"You now have access to the dark web from your home computer.<br>" +
"You have purchased a TOR router!\n" +
"You now have access to the dark web from your home computer.\n" +
"Use the scan/scan-analyze commands to search for the dark web connection.",
);
}

@ -35,7 +35,7 @@ function travel(p: IPlayer, router: IRouter, to: CityName): void {
p.loseMoney(cost, "other");
p.travel(to);
dialogBoxCreate(<>You are now in {to}!</>);
dialogBoxCreate(`You are now in ${to}!`);
router.toCity();
}

@ -1,3 +1,4 @@
import React from "react";
import { Message } from "./Message";
import { AugmentationNames } from "../Augmentation/data/AugmentationNames";
import { Router } from "../ui/GameRoot";
@ -22,15 +23,12 @@ function sendMessage(msg: Message, forced = false): void {
function showMessage(name: MessageFilenames): void {
const msg = Messages[name];
if (!(msg instanceof Message)) throw new Error("trying to display unexistent message");
const txt =
"Message received from unknown sender: <br><br>" +
"<i>" +
msg.msg +
"</i><br><br>" +
"This message was saved as " +
msg.filename +
" onto your home computer.";
dialogBoxCreate(txt);
dialogBoxCreate(
<>
Message received from unknown sender:<i>{msg.msg}</i>This message was saved as {msg.filename} onto your home
computer.
</>,
);
}
//Adds a message to a server
@ -127,20 +125,20 @@ const Messages: Record<MessageFilenames, Message> = {
MessageFilenames.Jumper0,
"I know you can sense it. I know you're searching for it. " +
"It's why you spend night after " +
"night at your computer. <br><br>It's real, I've seen it. And I can " +
"help you find it. But not right now. You're not ready yet.<br><br>" +
"Use this program to track your progress<br><br>" +
"The fl1ght.exe program was added to your home computer<br><br>" +
"night at your computer. \n\nIt's real, I've seen it. And I can " +
"help you find it. But not right now. You're not ready yet.\n\n" +
"Use this program to track your progress\n\n" +
"The fl1ght.exe program was added to your home computer\n\n" +
"-jump3R",
),
[MessageFilenames.Jumper1]: new Message(
MessageFilenames.Jumper1,
`Soon you will be contacted by a hacking group known as ${FactionNames.NiteSec}. ` +
"They can help you with your search. <br><br>" +
"They can help you with your search. \n\n" +
"You should join them, garner their favor, and " +
"exploit them for their Augmentations. But do not trust them. " +
"They are not what they seem. No one is.<br><br>" +
"They are not what they seem. No one is.\n\n" +
"-jump3R",
),
@ -148,21 +146,21 @@ const Messages: Record<MessageFilenames, Message> = {
MessageFilenames.Jumper2,
"Do not try to save the world. There is no world to save. If " +
"you want to find the truth, worry only about yourself. Ethics and " +
`morals will get you killed. <br><br>Watch out for a hacking group known as ${FactionNames.NiteSec}.` +
"<br><br>-jump3R",
`morals will get you killed. \n\nWatch out for a hacking group known as ${FactionNames.NiteSec}.` +
"\n\n-jump3R",
),
[MessageFilenames.Jumper3]: new Message(
MessageFilenames.Jumper3,
"You must learn to walk before you can run. And you must " +
`run before you can fly. Look for ${FactionNames.TheBlackHand}. <br><br>` +
"I.I.I.I <br><br>-jump3R",
`run before you can fly. Look for ${FactionNames.TheBlackHand}. \n\n` +
"I.I.I.I \n\n-jump3R",
),
[MessageFilenames.Jumper4]: new Message(
MessageFilenames.Jumper4,
"To find what you are searching for, you must understand the bits. " +
"The bits are all around us. The runners will help you.<br><br>" +
"The bits are all around us. The runners will help you.\n\n" +
"-jump3R",
),
@ -171,8 +169,8 @@ const Messages: Record<MessageFilenames, Message> = {
MessageFilenames.CyberSecTest,
"We've been watching you. Your skills are very impressive. But you're wasting " +
"your talents. If you join us, you can put your skills to good use and change " +
"the world for the better. If you join us, we can unlock your full potential. <br><br>" +
"But first, you must pass our test. Find and install the backdoor on our server. <br><br>" +
"the world for the better. If you join us, we can unlock your full potential. \n\n" +
"But first, you must pass our test. Find and install the backdoor on our server. \n\n" +
`-${FactionNames.CyberSec}`,
),
@ -181,17 +179,17 @@ const Messages: Record<MessageFilenames, Message> = {
"People say that the corrupted governments and corporations rule the world. " +
"Yes, maybe they do. But do you know who everyone really fears? People " +
"like us. Because they can't hide from us. Because they can't fight shadows " +
"and ideas with bullets. <br><br>" +
"Join us, and people will fear you, too. <br><br>" +
"and ideas with bullets. \n\n" +
"Join us, and people will fear you, too. \n\n" +
"Find and install the backdoor on our server, avmnite-02h. Then, we will contact you again." +
`<br><br>-${FactionNames.NiteSec}`,
`\n\n-${FactionNames.NiteSec}`,
),
[MessageFilenames.BitRunnersTest]: new Message(
MessageFilenames.BitRunnersTest,
"We know what you are doing. We know what drives you. We know " +
"what you are looking for. <br><br> " +
"We can help you find the answers.<br><br>" +
"what you are looking for. \n\n " +
"We can help you find the answers.\n\n" +
"run4theh111z",
),
@ -199,18 +197,18 @@ const Messages: Record<MessageFilenames, Message> = {
[MessageFilenames.TruthGazer]: new Message(
MessageFilenames.TruthGazer,
//"THE TRUTH CAN NO LONGER ESCAPE YOUR GAZE"
"@&*($#@&__TH3__#@A&#@*)__TRU1H__(*)&*)($#@&()E&R)W&<br>" +
"%@*$^$()@&$)$*@__CAN__()(@^#)@&@)#__N0__(#@&#)@&@&(<br>" +
"*(__LON6ER__^#)@)(()*#@)@__ESCAP3__)#(@(#@*@()@(#*$<br>" +
"@&*($#@&__TH3__#@A&#@*)__TRU1H__(*)&*)($#@&()E&R)W&\n" +
"%@*$^$()@&$)$*@__CAN__()(@^#)@&@)#__N0__(#@&#)@&@&(\n" +
"*(__LON6ER__^#)@)(()*#@)@__ESCAP3__)#(@(#@*@()@(#*$\n" +
"()@)#$*%)$#()$#__Y0UR__(*)$#()%(&(%)*!)($__GAZ3__#(",
),
[MessageFilenames.RedPill]: new Message(
MessageFilenames.RedPill,
//"FIND THE-CAVE"
"@)(#V%*N)@(#*)*C)@#%*)*V)@#(*%V@)(#VN%*)@#(*%<br>" +
")@B(*#%)@)M#B*%V)____FIND___#$@)#%(B*)@#(*%B)<br>" +
"@_#(%_@#M(BDSPOMB__THE-CAVE_#)$(*@#$)@#BNBEGB<br>" +
"@)(#V%*N)@(#*)*C)@#%*)*V)@#(*%V@)(#VN%*)@#(*%\n" +
")@B(*#%)@)M#B*%V)____FIND___#$@)#%(B*)@#(*%B)\n" +
"@_#(%_@#M(BDSPOMB__THE-CAVE_#)$(*@#$)@#BNBEGB\n" +
"DFLSMFVMV)#@($*)@#*$MV)@#(*$V)M#(*$)M@(#*VM$)",
),
};

@ -41,7 +41,7 @@ function wrapFunction(
const functionPath = tree.join(".");
const functionName = tree.pop();
if (typeof functionName !== "string") {
throw helpers.makeRuntimeRejectMsg(workerScript, "Failure occured while wrapping netscript api");
throw helpers.makeBasicErrorMsg(workerScript, "Failure occured while wrapping netscript api");
}
const ctx = {
workerScript,

@ -3,7 +3,6 @@ import { WorkerScript } from "./WorkerScript";
import { GetAllServers, GetServer } from "../Server/AllServers";
import { Player } from "../Player";
import { ScriptDeath } from "./ScriptDeath";
import { isString } from "../utils/helpers/isString";
import { numeralWrapper } from "../ui/numeralFormat";
import { ScriptArg } from "./ScriptArg";
import { CityName } from "../Locations/data/CityNames";
@ -40,8 +39,7 @@ export const helpers = {
number,
scriptArgs,
argsToString,
isScriptErrorMessage,
makeRuntimeRejectMsg,
makeBasicErrorMsg,
makeRuntimeErrorMsg,
resolveNetscriptRequestedThreads,
checkEnvFlags,
@ -114,15 +112,6 @@ function scriptArgs(ctx: NetscriptContext, args: unknown) {
return args;
}
/** Determines if the given msg string is an error created by makeRuntimeRejectMsg. */
function isScriptErrorMessage(msg: string): boolean {
if (!isString(msg)) {
return false;
}
const splitMsg = msg.split("|DELIMITER|");
return splitMsg.length == 4;
}
/** Convert multiple arguments for tprint or print into a single string. */
function argsToString(args: unknown[]): string {
let out = "";
@ -143,12 +132,11 @@ function argsToString(args: unknown[]): string {
}
/** Creates an error message string containing hostname, scriptname, and the error message msg */
function makeRuntimeRejectMsg(workerScript: WorkerScript, msg: string): string {
function makeBasicErrorMsg(workerScript: WorkerScript, msg: string): string {
for (const scriptUrl of workerScript.scriptRef.dependencies) {
msg = msg.replace(new RegExp(scriptUrl.url, "g"), scriptUrl.filename);
}
return "|DELIMITER|" + workerScript.hostname + "|DELIMITER|" + workerScript.name + "|DELIMITER|" + msg;
return msg;
}
/** Creates an error message string with a stack trace. */
@ -156,9 +144,9 @@ function makeRuntimeErrorMsg(ctx: NetscriptContext, msg: string): 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);
const workerScript = ctx.workerScript;
const caller = ctx.function;
const scripts = workerScript.getServer().scripts;
const ws = ctx.workerScript;
const caller = ctx.functionPath;
const scripts = ws.getServer().scripts;
const userstack = [];
for (const stackline of stack) {
let filename;
@ -216,10 +204,10 @@ function makeRuntimeErrorMsg(ctx: NetscriptContext, msg: string): string {
userstack.push(`${filename}:L${call.line}@${call.func}`);
}
workerScript.log(caller, () => msg);
log(ctx, () => msg);
let rejectMsg = `${caller}: ${msg}`;
if (userstack.length !== 0) rejectMsg += `<br><br>Stack:<br>${userstack.join("<br>")}`;
return makeRuntimeRejectMsg(workerScript, rejectMsg);
return makeBasicErrorMsg(ws, rejectMsg);
}
/** Validate requested number of threads for h/g/w options */
@ -230,14 +218,14 @@ function resolveNetscriptRequestedThreads(ctx: NetscriptContext, requestedThread
}
const requestedThreadsAsInt = requestedThreads | 0;
if (isNaN(requestedThreads) || requestedThreadsAsInt < 1) {
throw makeRuntimeRejectMsg(
ctx.workerScript,
throw makeRuntimeErrorMsg(
ctx,
`Invalid thread count passed to ${ctx.function}: ${requestedThreads}. Threads must be a positive number.`,
);
}
if (requestedThreadsAsInt > threads) {
throw makeRuntimeRejectMsg(
ctx.workerScript,
throw makeRuntimeErrorMsg(
ctx,
`Too many threads requested by ${ctx.function}. Requested: ${requestedThreads}. Has: ${threads}.`,
);
}
@ -258,17 +246,21 @@ function checkSingularityAccess(ctx: NetscriptContext): void {
/** Create an error if a script is dead or if concurrent ns function calls are made */
function checkEnvFlags(ctx: NetscriptContext): void {
const ws = ctx.workerScript;
if (ws.env.stopFlag) throw new ScriptDeath(ws);
if (ws.env.stopFlag) {
log(ctx, () => "Failed to run due to script being killed.");
throw new ScriptDeath(ws);
}
if (ws.env.runningFn && ctx.function !== "asleep") {
ws.errorMessage = makeRuntimeRejectMsg(
//This one has no error message so it will not create a dialog
if (ws.delayReject) ws.delayReject(new ScriptDeath(ws));
ws.errorMessage = makeBasicErrorMsg(
ws,
`Concurrent calls to Netscript functions are not allowed!
Did you forget to await hack(), grow(), or some other
promise-returning function?
Currently running: ${ws.env.runningFn} tried to run: ${ctx.function}`,
);
if (ws.delayReject) ws.delayReject(new ScriptDeath(ws));
throw new ScriptDeath(ws); //No idea if this is the right thing to throw
throw new ScriptDeath(ws);
}
}
@ -302,7 +294,8 @@ function updateDynamicRam(ctx: NetscriptContext, ramCost: number): void {
}
ws.dynamicRamUsage += ramCost;
if (ws.dynamicRamUsage > 1.01 * ws.ramUsage) {
ws.errorMessage = makeRuntimeRejectMsg(
log(ctx, () => "Insufficient static ram available.");
ws.errorMessage = makeBasicErrorMsg(
ws,
`Dynamic RAM usage calculated to be greater than initial RAM usage on fn: ${fnName}.
This is probably because you somehow circumvented the static RAM calculation.
@ -605,7 +598,7 @@ function getRunningScriptByArgs(
scriptArgs: ScriptArg[],
): RunningScript | null {
if (!Array.isArray(scriptArgs)) {
throw helpers.makeRuntimeRejectMsg(
throw helpers.makeBasicErrorMsg(
ctx.workerScript,
`Invalid scriptArgs argument passed into getRunningScript() from ${ctx.function}(). ` +
`This is probably a bug. Please report to game developer`,

@ -10,7 +10,7 @@ import { WorkerScriptStartStopEventEmitter } from "./WorkerScriptStartStopEventE
import { RunningScript } from "../Script/RunningScript";
import { GetServer } from "../Server/AllServers";
import { dialogBoxCreate } from "../ui/React/DialogBox";
import { errorDialog } from "../ui/React/DialogBox";
import { AddRecentScript } from "./RecentScripts";
import { Player } from "../Player";
import { ITutorial } from "../InteractiveTutorial";
@ -66,12 +66,7 @@ function stopAndCleanUpWorkerScript(ws: WorkerScript): void {
ws.env.stopFlag = false;
ws.atExit();
} catch (e: unknown) {
let message = e instanceof ScriptDeath ? e.errorMessage : String(e);
message = message.replace(/.*\|DELIMITER\|/, "");
dialogBoxCreate(
`Error trying to call atExit for script ${[ws.name, ...ws.args].join(" ")} on ${ws.hostname}\n ${message}`,
);
console.error(e);
errorDialog(e, `Error during atExit ${ws.name}@${ws.hostname} (PID - ${ws.pid}\n\n`);
}
ws.atExit = undefined;
}

@ -71,7 +71,7 @@ export async function executeJSScript(
const ns = workerScript.env.vars;
if (!loadedModule) {
throw helpers.makeRuntimeRejectMsg(
throw helpers.makeBasicErrorMsg(
workerScript,
`${script.filename} cannot be run because the script module won't load`,
);
@ -79,18 +79,19 @@ export async function executeJSScript(
// TODO: putting await in a non-async function yields unhelpful
// "SyntaxError: unexpected reserved word" with no line number information.
if (!loadedModule.main) {
throw helpers.makeRuntimeRejectMsg(
throw helpers.makeBasicErrorMsg(
workerScript,
`${script.filename} cannot be run because it does not have a main function.`,
);
}
if (!ns) {
throw helpers.makeRuntimeRejectMsg(
throw helpers.makeBasicErrorMsg(
workerScript,
`${script.filename} cannot be run because the NS object hasn't been constructed properly.`,
);
}
return loadedModule.main(ns);
await loadedModule.main(ns);
return;
}
function isDependencyOutOfDate(filename: string, scripts: Script[], scriptModuleSequenceNumber: number): boolean {

@ -24,10 +24,9 @@ import { Settings } from "./Settings/Settings";
import { generate } from "escodegen";
import { dialogBoxCreate } from "./ui/React/DialogBox";
import { dialogBoxCreate, errorDialog } from "./ui/React/DialogBox";
import { arrayToString } from "./utils/helpers/arrayToString";
import { roundToTwo } from "./utils/helpers/roundToTwo";
import { isString } from "./utils/helpers/isString";
import { parse } from "acorn";
import { simple as walksimple } from "acorn-walk";
@ -35,7 +34,6 @@ import { areFilesEqual } from "./Terminal/DirectoryHelpers";
import { Player } from "./Player";
import { Terminal } from "./Terminal";
import { ScriptArg } from "./Netscript/ScriptArg";
import { helpers } from "./Netscript/NetscriptHelpers";
export const NetscriptPorts: Map<number, IPort> = new Map();
@ -54,39 +52,8 @@ export function prestigeWorkerScripts(): void {
// JS script promises need a little massaging to have the same guarantees as netscript
// promises. This does said massaging and kicks the script off. It returns a promise
// that resolves or rejects when the corresponding worker script is done.
function startNetscript2Script(workerScript: WorkerScript): Promise<void> {
return new Promise<void>((resolve, reject) => {
executeJSScript(Player, workerScript.getServer().scripts, workerScript)
.then(() => {
resolve();
})
.catch((e) => reject(e));
}).catch((e) => {
if (e instanceof Error) {
if (e instanceof SyntaxError) {
workerScript.errorMessage = helpers.makeRuntimeRejectMsg(
workerScript,
e.message + " (sorry we can't be more helpful)",
);
} else {
workerScript.errorMessage = helpers.makeRuntimeRejectMsg(
workerScript,
e.message + ((e.stack && "\nstack:\n" + e.stack.toString()) || ""),
);
}
throw new ScriptDeath(workerScript);
} else if (helpers.isScriptErrorMessage(e)) {
workerScript.errorMessage = e;
throw new ScriptDeath(workerScript);
} else if (e instanceof ScriptDeath) {
throw e;
}
// Don't know what to do with it, let's try making an error message out of it
workerScript.errorMessage = helpers.makeRuntimeRejectMsg(workerScript, "" + e);
throw new ScriptDeath(workerScript);
});
}
const startNetscript2Script = (workerScript: WorkerScript): Promise<void> =>
executeJSScript(Player, workerScript.getServer().scripts, workerScript);
function startNetscript1Script(workerScript: WorkerScript): Promise<void> {
const code = workerScript.code;
@ -98,18 +65,16 @@ function startNetscript1Script(workerScript: WorkerScript): Promise<void> {
codeWithImports = importProcessingRes.code;
codeLineOffset = importProcessingRes.lineOffset;
} catch (e: unknown) {
dialogBoxCreate("Error processing Imports in " + workerScript.name + ":<br>" + String(e));
dialogBoxCreate(`Error processing Imports in ${workerScript.name} on ${workerScript.hostname}:\n\n${e}`);
workerScript.env.stopFlag = true;
killWorkerScript(workerScript);
return Promise.resolve();
}
function wrapNS1Layer(int: Interpreter, intLayer: unknown, path: string[] = []) {
//TODO: Better typing layers of interpreter scope and ns
interface BasicObject {
[key: string]: any;
}
const nsLayer = path.reduce((prev, newPath) => prev[newPath], workerScript.env.vars as BasicObject);
function wrapNS1Layer(int: Interpreter, intLayer: unknown, nsLayer = workerScript.env.vars as BasicObject) {
for (const [name, entry] of Object.entries(nsLayer)) {
if (typeof entry === "function") {
// Async functions need to be wrapped. See JS-Interpreter documentation
@ -121,22 +86,12 @@ function startNetscript1Script(workerScript: WorkerScript): Promise<void> {
const result = await entry(...args.map((arg) => int.pseudoToNative(arg)));
return callback(int.nativeToPseudo(result));
} catch (e: unknown) {
// TODO: Unify error handling, this was stolen from previous async handler
if (typeof e === "string") {
console.error(e);
const errorTextArray = e.split("|DELIMITER|");
const hostname = errorTextArray[1];
const scriptName = errorTextArray[2];
const errorMsg = errorTextArray[3];
let msg = `${scriptName}@${hostname}<br>`;
msg += "<br>";
msg += errorMsg;
dialogBoxCreate(msg);
// NS1 interpreter doesn't cleanly handle throwing. Need to show dialog here.
errorDialog(e, `RUNTIME ERROR:\n${workerScript.name}@${workerScript.hostname}\n\n`);
workerScript.env.stopFlag = true;
killWorkerScript(workerScript);
return;
}
}
};
int.setProperty(intLayer, name, int.createAsyncFunction(wrapper));
} else if (Array.isArray(entry) || typeof entry !== "object") {
@ -145,7 +100,7 @@ function startNetscript1Script(workerScript: WorkerScript): Promise<void> {
} else {
// new object layer, e.g. bladeburner
int.setProperty(intLayer, name, int.nativeToPseudo({}));
wrapNS1Layer(int, (intLayer as BasicObject).properties[name], [...path, name]);
wrapNS1Layer(int, (intLayer as BasicObject).properties[name], nsLayer[name]);
}
}
}
@ -154,18 +109,15 @@ function startNetscript1Script(workerScript: WorkerScript): Promise<void> {
try {
interpreter = new Interpreter(codeWithImports, wrapNS1Layer, codeLineOffset);
} catch (e: unknown) {
dialogBoxCreate("Syntax ERROR in " + workerScript.name + ":<br>" + String(e));
dialogBoxCreate(`Syntax ERROR in ${workerScript.name} on ${workerScript.hostname}:\n\n${String(e)}`);
workerScript.env.stopFlag = true;
killWorkerScript(workerScript);
return Promise.resolve();
}
return new Promise(function (resolve, reject) {
return new Promise((resolve) => {
function runInterpreter(): void {
try {
if (workerScript.env.stopFlag) {
return reject(new ScriptDeath(workerScript));
}
if (workerScript.env.stopFlag) resolve();
let more = true;
let i = 0;
@ -179,29 +131,8 @@ function startNetscript1Script(workerScript: WorkerScript): Promise<void> {
} else {
resolve();
}
} catch (_e: unknown) {
let e = String(_e);
if (!helpers.isScriptErrorMessage(e)) {
e = helpers.makeRuntimeRejectMsg(workerScript, e);
}
workerScript.errorMessage = e;
return reject(new ScriptDeath(workerScript));
}
}
try {
runInterpreter();
} catch (e: unknown) {
if (isString(e)) {
workerScript.errorMessage = e;
return reject(new ScriptDeath(workerScript));
} else if (e instanceof ScriptDeath) {
return reject(e);
} else {
console.error(e);
return reject(new ScriptDeath(workerScript));
}
}
});
}
@ -430,58 +361,19 @@ function createAndAddWorkerScript(runningScriptObj: RunningScript, server: BaseS
// running status to false
scriptExecution
.then(function () {
workerScript.env.stopFlag = true;
// On natural death, the earnings are transfered to the parent if it still exists.
if (parent !== undefined && !parent.env.stopFlag) {
if (parent && !parent.env.stopFlag) {
parent.scriptRef.onlineExpGained += runningScriptObj.onlineExpGained;
parent.scriptRef.onlineMoneyMade += runningScriptObj.onlineMoneyMade;
}
killWorkerScript(workerScript);
workerScript.log("", () => "Script finished running");
})
.catch(function (e) {
if (e instanceof Error) {
dialogBoxCreate("Script runtime unknown error. This is a bug please contact game developer");
console.error("Evaluating workerscript returns an Error. THIS SHOULDN'T HAPPEN: " + e.toString());
return;
} else if (e instanceof ScriptDeath) {
if (helpers.isScriptErrorMessage(workerScript.errorMessage)) {
const errorTextArray = workerScript.errorMessage.split("|DELIMITER|");
if (errorTextArray.length != 4) {
console.error("ERROR: Something wrong with Error text in evaluator...");
console.error("Error text: " + workerScript.errorMessage);
return;
}
const hostname = errorTextArray[1];
const scriptName = errorTextArray[2];
const errorMsg = errorTextArray[3];
let msg = `RUNTIME ERROR<br>${scriptName}@${hostname} (PID - ${workerScript.pid})<br>`;
if (workerScript.args.length > 0) {
msg += `Args: ${arrayToString(workerScript.args)}<br>`;
}
msg += "<br>";
msg += errorMsg;
dialogBoxCreate(msg);
workerScript.log("", () => "Script crashed with runtime error");
} else {
workerScript.log("", () => "Script killed");
return; // Already killed, so stop here
}
} else if (helpers.isScriptErrorMessage(e)) {
dialogBoxCreate("Script runtime unknown error. This is a bug please contact game developer");
console.error(
"ERROR: Evaluating workerscript returns only error message rather than WorkerScript object. THIS SHOULDN'T HAPPEN: " +
e.toString(),
);
return;
} else {
dialogBoxCreate("An unknown script died for an unknown reason. This is a bug please contact game dev");
console.error(e);
}
errorDialog(e, `RUNTIME ERROR\n${workerScript.name}@${workerScript.hostname} (PID - ${workerScript.pid})\n\n`);
let logText = "Script crashed due to an error.";
if (e instanceof ScriptDeath) logText = "Script killed.";
workerScript.log("", () => logText);
killWorkerScript(workerScript);
});

@ -27,7 +27,6 @@ import { AwardNFG, v1APIBreak } from "./utils/v1APIBreak";
import { AugmentationNames } from "./Augmentation/data/AugmentationNames";
import { PlayerOwnedAugmentation } from "./Augmentation/PlayerOwnedAugmentation";
import { LocationName } from "./Locations/data/LocationNames";
import { SxProps } from "@mui/system";
import { PlayerObject } from "./PersonObjects/Player/PlayerObject";
import { pushGameSaved } from "./Electron";
import { defaultMonacoTheme } from "./ScriptEditor/ui/themes";
@ -759,27 +758,14 @@ function createScamUpdateText(): void {
}
}
const resets: SxProps = {
"& h1, & h2, & h3, & h4, & p, & a, & ul": {
margin: 0,
color: Settings.theme.primary,
whiteSpace: "initial",
},
"& ul": {
paddingLeft: "1.5em",
lineHeight: 1.5,
},
};
function createNewUpdateText(): void {
setTimeout(
() =>
dialogBoxCreate(
"New update!<br>" +
"New update!\n" +
"Please report any bugs/issues through the GitHub repository " +
"or the Bitburner subreddit (reddit.com/r/bitburner).<br><br>" +
"or the Bitburner subreddit (reddit.com/r/bitburner).\n\n" +
CONSTANTS.LatestUpdate,
resets,
),
1000,
);
@ -788,11 +774,10 @@ function createNewUpdateText(): void {
function createBetaUpdateText(): void {
dialogBoxCreate(
"You are playing on the beta environment! This branch of the game " +
"features the latest developments in the game. This version may be unstable.<br>" +
"features the latest developments in the game. This version may be unstable.\n" +
"Please report any bugs/issues through the github repository (https://github.com/danielyxie/bitburner/issues) " +
"or the Bitburner subreddit (reddit.com/r/bitburner).<br><br>" +
"or the Bitburner subreddit (reddit.com/r/bitburner).\n\n" +
CONSTANTS.LatestUpdate,
resets,
);
}

@ -1,6 +1,6 @@
import { AutocompleteData, NS } from "../ScriptEditor/NetscriptDefinitions";
export interface ScriptModule {
main?: (ns: NS) => Promise<void>;
main?: (ns: NS) => unknown;
autocomplete?: (data: AutocompleteData, flags: string[]) => unknown;
}

@ -1,24 +1,7 @@
import { ScriptDeath } from "./Netscript/ScriptDeath";
import { helpers } from "./Netscript/NetscriptHelpers";
import { dialogBoxCreate } from "./ui/React/DialogBox";
import { errorDialog } from "./ui/React/DialogBox";
export function setupUncaughtPromiseHandler(): void {
window.addEventListener("unhandledrejection", function (e) {
if (helpers.isScriptErrorMessage(e.reason)) {
const errorTextArray = e.reason.split("|DELIMITER|");
const hostname = errorTextArray[1];
const scriptName = errorTextArray[2];
const errorMsg = errorTextArray[3];
let msg = `UNCAUGHT PROMISE ERROR<br>You forgot to await a promise<br>${scriptName}@${hostname}<br>`;
msg += "<br>";
msg += errorMsg;
dialogBoxCreate(msg);
} else if (e.reason instanceof ScriptDeath) {
const msg =
`UNCAUGHT PROMISE ERROR<br>You forgot to await a promise<br>${e.reason.name}@${e.reason.hostname} (PID - ${e.reason.pid})<br>` +
`Maybe hack / grow / weaken ?`;
dialogBoxCreate(msg);
}
});
window.addEventListener("unhandledrejection", (e) =>
errorDialog(e.reason, "UNCAUGHT PROMISE ERROR\nYou forgot to await a promise\nmaybe hack / grow / weaken ?\n"),
);
}

@ -1,13 +1,29 @@
import { AlertEvents } from "./AlertManager";
import React from "react";
import { SxProps } from "@mui/system";
import { Typography } from "@mui/material";
import { ScriptDeath } from "../../Netscript/ScriptDeath";
export function dialogBoxCreate(txt: string | JSX.Element, styles?: SxProps): void {
if (typeof txt !== "string") {
AlertEvents.emit(txt);
} else {
AlertEvents.emit(<Typography component="span" sx={styles} dangerouslySetInnerHTML={{ __html: txt }} />);
}
export function dialogBoxCreate(txt: string | JSX.Element): void {
AlertEvents.emit(typeof txt === "string" ? <Typography component="span">{txt}</Typography> : txt);
}
export function errorDialog(e: unknown, initialText = "") {
let errorText = "";
if (typeof e === "string") errorText = e;
else if (e instanceof ScriptDeath) {
if (!e.errorMessage) return; //No need for a dialog for an empty ScriptDeath
errorText = e.errorMessage;
} else if (e instanceof SyntaxError) errorText = e.message + " (sorry we can't be more helpful)";
else if (e instanceof Error) errorText = e.message + (e.stack ? `\nstack:\n${e.stack.toString()}` : "");
else {
errorText = "An unknown error was thrown, see console.";
console.error(e);
}
if (!initialText) {
if (e instanceof ScriptDeath) initialText = `${e.name}@${e.hostname} (PID - ${e.pid})\n\n`;
}
dialogBoxCreate(initialText + errorText);
}