CODEBASE: Fix lint errors 1 (#1732)

This commit is contained in:
catloversg 2024-11-04 13:35:14 +07:00 committed by GitHub
parent f7ee3a340f
commit f6502dd490
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
55 changed files with 252 additions and 255 deletions

@ -34,6 +34,7 @@ module.exports = {
"@typescript-eslint/ban-ts-comment": "off", "@typescript-eslint/ban-ts-comment": "off",
"@typescript-eslint/no-explicit-any": "off", "@typescript-eslint/no-explicit-any": "off",
"react/no-unescaped-entities": "off", "react/no-unescaped-entities": "off",
"@typescript-eslint/restrict-template-expressions": "off",
}, },
settings: { settings: {
react: { react: {

@ -31,4 +31,21 @@ declare global {
}; };
}; };
} }
/**
* "loader" is not exposed in the public API.
*/
module "monaco-editor" {
namespace languages {
interface ILanguageExtensionPoint {
loader: () => Promise<{
language: {
tokenizer: {
root: any[];
};
};
}>;
}
}
}
} }

@ -159,7 +159,7 @@ function sanitizeRewardType(rewardType: CodingContractRewardType): CodingContrac
try { try {
return Factions[fac].getInfo().offerHackingWork; return Factions[fac].getInfo().offerHackingWork;
} catch (e) { } catch (e) {
console.error(`Error when trying to filter Hacking Factions for Coding Contract Generation: ${e}`); console.error("Error when trying to filter Hacking Factions for Coding Contract Generation", e);
return false; return false;
} }
}); });

@ -207,8 +207,8 @@ export function sellMaterial(material: Material, amount: string, price: string):
try { try {
if (temp.includes("MP")) throw "Only one reference to MP is allowed in sell price."; if (temp.includes("MP")) throw "Only one reference to MP is allowed in sell price.";
temp = eval?.(temp); temp = eval?.(temp);
} catch (e) { } catch (error) {
throw new Error("Invalid value or expression for sell price field: " + e); throw new Error("Invalid value or expression for sell price field", { cause: error });
} }
if (temp == null || isNaN(parseFloat(temp))) { if (temp == null || isNaN(parseFloat(temp))) {
@ -231,8 +231,8 @@ export function sellMaterial(material: Material, amount: string, price: string):
tempQty = tempQty.replace(/INV/g, material.productionAmount.toString()); tempQty = tempQty.replace(/INV/g, material.productionAmount.toString());
try { try {
tempQty = eval?.(tempQty); tempQty = eval?.(tempQty);
} catch (e) { } catch (error) {
throw new Error("Invalid value or expression for sell quantity field: " + e); throw new Error("Invalid value or expression for sell quantity field", { cause: error });
} }
if (tempQty == null || isNaN(parseFloat(tempQty))) { if (tempQty == null || isNaN(parseFloat(tempQty))) {
@ -263,8 +263,8 @@ export function sellProduct(product: Product, city: CityName, amt: string, price
try { try {
if (temp.includes("MP")) throw "Only one reference to MP is allowed in sell price."; if (temp.includes("MP")) throw "Only one reference to MP is allowed in sell price.";
temp = eval?.(temp); temp = eval?.(temp);
} catch (e) { } catch (error) {
throw new Error("Invalid value or expression for sell price field: " + e); throw new Error("Invalid value or expression for sell price field.", { cause: error });
} }
if (temp == null || isNaN(parseFloat(temp))) { if (temp == null || isNaN(parseFloat(temp))) {
throw new Error("Invalid value or expression for sell price field."); throw new Error("Invalid value or expression for sell price field.");
@ -291,8 +291,8 @@ export function sellProduct(product: Product, city: CityName, amt: string, price
temp = temp.replace(/INV/g, product.cityData[city].stored.toString()); temp = temp.replace(/INV/g, product.cityData[city].stored.toString());
try { try {
temp = eval?.(temp); temp = eval?.(temp);
} catch (e) { } catch (error) {
throw new Error("Invalid value or expression for sell quantity field: " + e); throw new Error("Invalid value or expression for sell quantity field", { cause: error });
} }
if (temp == null || isNaN(parseFloat(temp))) { if (temp == null || isNaN(parseFloat(temp))) {
@ -585,13 +585,16 @@ Attempted export amount: ${amount}`);
} }
if (!error && isNaN(evaluated)) error = "evaluated value is NaN"; if (!error && isNaN(evaluated)) error = "evaluated value is NaN";
if (error) { if (error) {
throw new Error(`Error while trying to set the exported amount of ${material.name}. throw new Error(
`Error while trying to set the exported amount of ${material.name}.
Error occurred while testing keyword replacement with ${testReplacement}. Error occurred while testing keyword replacement with ${testReplacement}.
Your input: ${amount} Your input: ${amount}
Sanitized input: ${sanitizedAmt} Sanitized input: ${sanitizedAmt}
Input after replacement: ${replaced} Input after replacement: ${replaced}
Evaluated value: ${evaluated} Evaluated value: ${evaluated}` +
Error encountered: ${error}`); // eslint-disable-next-line @typescript-eslint/no-base-to-string
`Error encountered: ${error}`,
);
} }
} }

@ -29,8 +29,8 @@ export function ExpandNewCity(props: IProps): React.ReactElement {
function expand(): void { function expand(): void {
try { try {
purchaseOffice(corp, division, city); purchaseOffice(corp, division, city);
} catch (err) { } catch (error) {
dialogBoxCreate(err + ""); dialogBoxCreate(String(error));
return; return;
} }

@ -39,8 +39,8 @@ export function NewDivisionTab(props: IProps): React.ReactElement {
if (disabledText) return; if (disabledText) return;
try { try {
createDivision(corp, industry, name); createDivision(corp, industry, name);
} catch (err) { } catch (error) {
dialogBoxCreate(err + ""); dialogBoxCreate(String(error));
return; return;
} }

@ -43,8 +43,8 @@ export function BuybackSharesModal(props: IProps): React.ReactElement {
props.onClose(); props.onClose();
props.rerender(); props.rerender();
setShares(NaN); setShares(NaN);
} catch (err) { } catch (error) {
dialogBoxCreate(`${err}`); dialogBoxCreate(String(error));
} }
} }

@ -59,8 +59,8 @@ export function ExportModal(props: ExportModalProps): React.ReactElement {
try { try {
if (!targetDivision || !targetCity) return; if (!targetDivision || !targetCity) return;
actions.exportMaterial(targetDivision, targetCity, props.mat, exportAmount); actions.exportMaterial(targetDivision, targetCity, props.mat, exportAmount);
} catch (err) { } catch (error) {
dialogBoxCreate(err + ""); dialogBoxCreate(String(error));
} }
props.onClose(); props.onClose();
} }

@ -37,8 +37,8 @@ export function FindInvestorsModal(props: IProps): React.ReactElement {
); );
props.onClose(); props.onClose();
props.rerender(); props.rerender();
} catch (err) { } catch (error) {
dialogBoxCreate(`${err}`); dialogBoxCreate(String(error));
} }
} }

@ -45,8 +45,8 @@ export function GoPublicModal(props: IProps): React.ReactElement {
props.onClose(); props.onClose();
props.rerender(); props.rerender();
setShares(NaN); setShares(NaN);
} catch (err) { } catch (error) {
dialogBoxCreate(`${err}`); dialogBoxCreate(String(error));
} }
} }

@ -27,8 +27,8 @@ export function IssueDividendsModal(props: IProps): React.ReactElement {
if (percent === null) return; if (percent === null) return;
try { try {
actions.issueDividends(corp, percent / 100); actions.issueDividends(corp, percent / 100);
} catch (err) { } catch (error) {
dialogBoxCreate(err + ""); dialogBoxCreate(String(error));
} }
props.onClose(); props.onClose();

@ -55,8 +55,8 @@ export function IssueNewSharesModal(props: IProps): React.ReactElement {
); );
props.onClose(); props.onClose();
props.rerender(); props.rerender();
} catch (err) { } catch (error) {
dialogBoxCreate(`${err}`); dialogBoxCreate(String(error));
} }
} }

@ -47,8 +47,8 @@ export function MakeProductModal(props: IProps): React.ReactElement {
if (isNaN(design) || isNaN(marketing)) return; if (isNaN(design) || isNaN(marketing)) return;
try { try {
actions.makeProduct(corp, division, city, name, design, marketing); actions.makeProduct(corp, division, city, name, design, marketing);
} catch (err) { } catch (error) {
dialogBoxCreate(err + ""); dialogBoxCreate(String(error));
} }
props.onClose(); props.onClose();
} }

@ -67,8 +67,8 @@ function BulkPurchaseSection(props: IBPProps): React.ReactElement {
function bulkPurchase(): void { function bulkPurchase(): void {
try { try {
actions.bulkPurchase(corp, division, props.warehouse, props.mat, parseFloat(buyAmt)); actions.bulkPurchase(corp, division, props.warehouse, props.mat, parseFloat(buyAmt));
} catch (err) { } catch (error) {
dialogBoxCreate(err + ""); dialogBoxCreate(String(error));
} }
props.onClose(); props.onClose();
} }
@ -119,8 +119,8 @@ export function PurchaseMaterialModal(props: IProps): React.ReactElement {
if (buyAmt === null) return; if (buyAmt === null) return;
try { try {
actions.buyMaterial(division, props.mat, buyAmt); actions.buyMaterial(division, props.mat, buyAmt);
} catch (err) { } catch (error) {
dialogBoxCreate(err + ""); dialogBoxCreate(String(error));
} }
props.onClose(); props.onClose();

@ -39,8 +39,8 @@ function Upgrade({ n, division }: INodeProps): React.ReactElement {
if (n === null || disabled) return; if (n === null || disabled) return;
try { try {
actions.research(division, n.researchName); actions.research(division, n.researchName);
} catch (err) { } catch (error) {
dialogBoxCreate(err + ""); dialogBoxCreate(String(error));
return; return;
} }

@ -24,8 +24,8 @@ export function SellMaterialModal(props: IProps): React.ReactElement {
function sellMaterial(): void { function sellMaterial(): void {
try { try {
actions.sellMaterial(props.mat, amt, price); actions.sellMaterial(props.mat, amt, price);
} catch (err) { } catch (error) {
dialogBoxCreate(err + ""); dialogBoxCreate(String(error));
} }
props.onClose(); props.onClose();
} }

@ -31,8 +31,8 @@ export function SellProductModal(props: IProps): React.ReactElement {
function sellProduct(): void { function sellProduct(): void {
try { try {
actions.sellProduct(props.product, props.city, iQty, px, checked); actions.sellProduct(props.product, props.city, iQty, px, checked);
} catch (err) { } catch (error) {
dialogBoxCreate(err + ""); dialogBoxCreate(String(error));
} }
props.onClose(); props.onClose();

@ -27,8 +27,9 @@ function SSoption(props: ISSoptionProps): React.ReactElement {
const matName = props.matName; const matName = props.matName;
const material = props.warehouse.materials[matName]; const material = props.warehouse.materials[matName];
setSmartSupplyOption(props.warehouse, material, newValue); setSmartSupplyOption(props.warehouse, material, newValue);
} catch (err) { } catch (error) {
dialogBoxCreate(err + ""); dialogBoxCreate(String(error));
return;
} }
setChecked(newValue); setChecked(newValue);
} }
@ -40,8 +41,9 @@ function SSoption(props: ISSoptionProps): React.ReactElement {
const matName = props.matName; const matName = props.matName;
const material = props.warehouse.materials[matName]; const material = props.warehouse.materials[matName];
setSmartSupplyOption(props.warehouse, material, newValue); setSmartSupplyOption(props.warehouse, material, newValue);
} catch (err) { } catch (error) {
dialogBoxCreate(err + ""); dialogBoxCreate(String(error));
return;
} }
setChecked(newValue); setChecked(newValue);
} }

@ -12,7 +12,7 @@ import { CONSTANTS } from "./Constants";
import { commitHash } from "./utils/helpers/commitHash"; import { commitHash } from "./utils/helpers/commitHash";
import { resolveFilePath } from "./Paths/FilePath"; import { resolveFilePath } from "./Paths/FilePath";
import { hasScriptExtension } from "./Paths/ScriptFilePath"; import { hasScriptExtension } from "./Paths/ScriptFilePath";
import { handleGetSaveDataError } from "./Netscript/ErrorMessages"; import { handleGetSaveDataInfoError } from "./Netscript/ErrorMessages";
interface IReturnWebStatus extends IReturnStatus { interface IReturnWebStatus extends IReturnStatus {
data?: Record<string, unknown>; data?: Record<string, unknown>;
@ -103,14 +103,14 @@ function initAppNotifier(): void {
const funcs = { const funcs = {
terminal: (message: string, type?: string) => { terminal: (message: string, type?: string) => {
const typesFn: Record<string, (s: string) => void> = { const typesFn: Record<string, (s: string) => void> = {
info: Terminal.info, info: (s) => Terminal.info(s),
warn: Terminal.warn, warn: (s) => Terminal.warn(s),
error: Terminal.error, error: (s) => Terminal.error(s),
success: Terminal.success, success: (s) => Terminal.success(s),
}; };
let fn; let fn;
if (type) fn = typesFn[type]; if (type) fn = typesFn[type];
if (!fn) fn = Terminal.print; if (!fn) fn = (s: string) => Terminal.print(s);
fn.bind(Terminal)(message); fn.bind(Terminal)(message);
}, },
toast: (message: string, type: ToastVariant, duration = 2000) => SnackbarEvents.emit(message, type, duration), toast: (message: string, type: ToastVariant, duration = 2000) => SnackbarEvents.emit(message, type, duration),
@ -124,12 +124,10 @@ function initSaveFunctions(): void {
const funcs = { const funcs = {
triggerSave: (): Promise<void> => saveObject.saveGame(true), triggerSave: (): Promise<void> => saveObject.saveGame(true),
triggerGameExport: (): void => { triggerGameExport: (): void => {
try { saveObject.exportGame().catch((error) => {
saveObject.exportGame();
} catch (error) {
console.error(error); console.error(error);
SnackbarEvents.emit("Could not export game.", ToastVariant.ERROR, 2000); SnackbarEvents.emit("Could not export game.", ToastVariant.ERROR, 2000);
} });
}, },
triggerScriptsExport: (): void => exportScripts("*", Player.getHomeComputer()), triggerScriptsExport: (): void => exportScripts("*", Player.getHomeComputer()),
getSaveData: async (): Promise<{ save: SaveData; fileName: string }> => { getSaveData: async (): Promise<{ save: SaveData; fileName: string }> => {
@ -159,22 +157,28 @@ function initElectronBridge(): void {
const bridge = window.electronBridge; const bridge = window.electronBridge;
if (!bridge) return; if (!bridge) return;
bridge.receive("get-save-data-request", async () => { bridge.receive("get-save-data-request", () => {
let saveData; window.appSaveFns
try { .getSaveData()
saveData = await window.appSaveFns.getSaveData(); .then((saveData) => {
} catch (error) {
handleGetSaveDataError(error);
return;
}
bridge.send("get-save-data-response", saveData); bridge.send("get-save-data-response", saveData);
})
.catch((error) => {
handleGetSaveDataInfoError(error);
}); });
bridge.receive("get-save-info-request", async (saveData: unknown) => { });
bridge.receive("get-save-info-request", (saveData: unknown) => {
if (typeof saveData !== "string" && !(saveData instanceof Uint8Array)) { if (typeof saveData !== "string" && !(saveData instanceof Uint8Array)) {
throw new Error("Error while trying to get save info"); throw new Error("Error while trying to get save info");
} }
const saveInfo = await window.appSaveFns.getSaveInfo(saveData); window.appSaveFns
.getSaveInfo(saveData)
.then((saveInfo) => {
bridge.send("get-save-info-response", saveInfo); bridge.send("get-save-info-response", saveInfo);
})
.catch((error) => {
handleGetSaveDataInfoError(error, true);
});
}); });
bridge.receive("push-save-request", (params: unknown) => { bridge.receive("push-save-request", (params: unknown) => {
if (typeof params !== "object") throw new Error("Error trying to push save request"); if (typeof params !== "object") throw new Error("Error trying to push save request");
@ -182,7 +186,7 @@ function initElectronBridge(): void {
window.appSaveFns.pushSaveData(save, automatic); window.appSaveFns.pushSaveData(save, automatic);
}); });
bridge.receive("trigger-save", () => { bridge.receive("trigger-save", () => {
return window.appSaveFns window.appSaveFns
.triggerSave() .triggerSave()
.then(() => { .then(() => {
bridge.send("save-completed"); bridge.send("save-completed");

@ -97,7 +97,7 @@ function MainPage({ faction, rerender, onAugmentations }: IMainProps): React.Rea
startWork(); startWork();
} }
// We have a special flag for whether the player this faction is the player's // We have a special flag for whether this faction is the player's
// gang faction because if the player has a gang, they cannot do any other action // gang faction because if the player has a gang, they cannot do any other action
const isPlayersGang = Player.gang && Player.getGangName() === faction.name; const isPlayersGang = Player.gang && Player.getGangName() === faction.name;

@ -90,6 +90,7 @@ export const GameOptionsSidebar = (props: IProps): React.ReactElement => {
try { try {
await saveObject.importGame(importData.saveData); await saveObject.importGame(importData.saveData);
} catch (e: unknown) { } catch (e: unknown) {
console.error(e);
SnackbarEvents.emit(String(e), ToastVariant.ERROR, 5000); SnackbarEvents.emit(String(e), ToastVariant.ERROR, 5000);
} }

@ -112,7 +112,7 @@ export class Gang {
this.processTerritoryAndPowerGains(cycles); this.processTerritoryAndPowerGains(cycles);
this.storedCycles -= cycles; this.storedCycles -= cycles;
} catch (e: unknown) { } catch (e: unknown) {
console.error(`Exception caught when processing Gang: ${e}`); console.error("Exception caught when processing Gang", e);
} }
// Handle "nextUpdate" resolver after this update // Handle "nextUpdate" resolver after this update

@ -1,96 +0,0 @@
/** @param {NS} ns */
export async function main(ns) {
let result;
do {
const board = ns.go.getBoardState();
const validMoves = ns.go.analysis.getValidMoves();
const [growX, growY] = getGrowMove(board, validMoves);
const [randX, randY] = getRandomMove(board, validMoves);
// Try to pick a grow move, otherwise choose a random move
const x = growX ?? randX;
const y = growY ?? randY;
if (x === undefined) {
// Pass turn if no moves are found
result = await ns.go.passTurn();
} else {
// Play the selected move
result = await ns.go.makeMove(x, y);
}
await ns.sleep(100);
} while (result?.type !== "gameOver" && result?.type !== "pass");
// After the opponent passes, end the game by passing as well
await ns.go.passTurn();
}
/**
* Choose one of the empty points on the board at random to play
*/
const getRandomMove = (board, validMoves) => {
const moveOptions = [];
const size = board[0].length;
// Look through all the points on the board
for (let x = 0; x < size; x++) {
for (let y = 0; y < size; y++) {
// Make sure the point is a valid move
const isValidMove = validMoves[x][y];
// Leave some spaces to make it harder to capture our pieces
const isNotReservedSpace = x % 2 || y % 2;
if (isValidMove && isNotReservedSpace) {
moveOptions.push([x, y]);
}
}
}
// Choose one of the found moves at random
const randomIndex = Math.floor(Math.random() * moveOptions.length);
return moveOptions[randomIndex] ?? [];
};
/**
* Choose a point connected to a friendly stone to play
*/
const getGrowMove = (board, validMoves) => {
const moveOptions = [];
const size = board[0].length;
// Look through all the points on the board
for (let x = 0; x < size; x++) {
for (let y = 0; y < size; y++) {
// make sure the move is valid
const isValidMove = validMoves[x][y];
// Leave some open spaces to make it harder to capture our pieces
const isNotReservedSpace = x % 2 || y % 2;
// Make sure we are connected to a friendly piece
const neighbors = getNeighbors(board, x, y);
const hasFriendlyNeighbor = neighbors.includes("X");
if (isValidMove && isNotReservedSpace && hasFriendlyNeighbor) {
moveOptions.push([x, y]);
}
}
}
// Choose one of the found moves at random
const randomIndex = Math.floor(Math.random() * moveOptions.length);
return moveOptions[randomIndex] ?? [];
};
/**
* Find all adjacent points in the four connected directions
*/
const getNeighbors = (board, x, y) => {
const north = board[x + 1]?.[y];
const east = board[x][y + 1];
const south = board[x - 1]?.[y];
const west = board[x]?.[y - 1];
return [north, east, south, west];
};

@ -20,6 +20,7 @@ import { GoSubnetSearch } from "./GoSubnetSearch";
import { CorruptableText } from "../../ui/React/CorruptableText"; import { CorruptableText } from "../../ui/React/CorruptableText";
import { makeAIMove, resetAI, resolveCurrentTurn } from "../boardAnalysis/goAI"; import { makeAIMove, resetAI, resolveCurrentTurn } from "../boardAnalysis/goAI";
import { GoScoreExplanation } from "./GoScoreExplanation"; import { GoScoreExplanation } from "./GoScoreExplanation";
import { exceptionAlert } from "../../utils/helpers/exceptionAlert";
interface GoGameboardWrapperProps { interface GoGameboardWrapperProps {
showInstructions: () => void; showInstructions: () => void;
@ -63,7 +64,7 @@ export function GoGameboardWrapper({ showInstructions }: GoGameboardWrapperProps
// Do not implement useCallback for this function without ensuring GoGameboard still rerenders for every move // Do not implement useCallback for this function without ensuring GoGameboard still rerenders for every move
// Currently this function changing is what triggers a GoGameboard rerender, which is needed // Currently this function changing is what triggers a GoGameboard rerender, which is needed
async function clickHandler(x: number, y: number) { function clickHandler(x: number, y: number) {
if (showPriorMove) { if (showPriorMove) {
SnackbarEvents.emit( SnackbarEvents.emit(
`Currently showing a past board state. Please disable "Show previous move" to continue.`, `Currently showing a past board state. Please disable "Show previous move" to continue.`,
@ -94,7 +95,7 @@ export function GoGameboardWrapper({ showInstructions }: GoGameboardWrapperProps
const didUpdateBoard = makeMove(boardState, x, y, currentPlayer); const didUpdateBoard = makeMove(boardState, x, y, currentPlayer);
if (didUpdateBoard) { if (didUpdateBoard) {
rerender(); rerender();
takeAiTurn(boardState); takeAiTurn(boardState).catch((error) => exceptionAlert(error));
} }
} }
@ -113,7 +114,7 @@ export function GoGameboardWrapper({ showInstructions }: GoGameboardWrapperProps
} }
setTimeout(() => { setTimeout(() => {
takeAiTurn(boardState); takeAiTurn(boardState).catch((error) => exceptionAlert(error));
}, 100); }, 100);
} }

@ -99,13 +99,13 @@ export function handleUnknownError(e: unknown, ws: WorkerScript | null = null, i
Error has been logged to the console.\n\nType of error: ${typeof e}\nValue of error: ${e}`; Error has been logged to the console.\n\nType of error: ${typeof e}\nValue of error: ${e}`;
e = ws ? basicErrorMessage(ws, msg, "UNKNOWN") : msg; e = ws ? basicErrorMessage(ws, msg, "UNKNOWN") : msg;
} }
dialogBoxCreate(initialText + e); dialogBoxCreate(initialText + String(e));
} }
/** Use this handler to handle the error when we call getSaveData function */ /** Use this handler to handle the error when we call getSaveData function or getSaveInfo function */
export function handleGetSaveDataError(error: unknown) { export function handleGetSaveDataInfoError(error: unknown, fromGetSaveInfo = false) {
console.error(error); console.error(error);
let errorMessage = `Cannot get save data. Error: ${error}.`; let errorMessage = `Cannot get save ${fromGetSaveInfo ? "info" : "data"}. Error: ${error}.`;
if (error instanceof RangeError) { if (error instanceof RangeError) {
errorMessage += " This may be because the save data is too large."; errorMessage += " This may be because the save data is too large.";
} }

@ -232,7 +232,7 @@ function spawnOptions(ctx: NetscriptContext, threadOrOption: unknown): CompleteS
function mapToString(map: Map<unknown, unknown>): string { function mapToString(map: Map<unknown, unknown>): string {
const formattedMap = [...map] const formattedMap = [...map]
.map((m) => { .map((m) => {
return `${m[0]} => ${m[1]}`; return `${String(m[0])} => ${String(m[1])}`;
}) })
.join("; "); .join("; ");
return `< Map: ${formattedMap} >`; return `< Map: ${formattedMap} >`;
@ -245,7 +245,7 @@ function setToString(set: Set<unknown>): string {
/** Convert multiple arguments for tprint or print into a single string. */ /** Convert multiple arguments for tprint or print into a single string. */
function argsToString(args: unknown[]): string { function argsToString(args: unknown[]): string {
// Reduce array of args into a single output string // Reduce array of args into a single output string
return args.reduce((out, arg) => { return args.reduce((out: string, arg) => {
if (arg === null) { if (arg === null) {
return (out += "null"); return (out += "null");
} }
@ -264,12 +264,13 @@ function argsToString(args: unknown[]): string {
return (out += setToString(nativeArg)); return (out += setToString(nativeArg));
} }
if (typeof nativeArg === "object") { if (typeof nativeArg === "object") {
return (out += JSON.stringify(nativeArg, (_, value) => { return (out += JSON.stringify(nativeArg, (_, value: unknown) => {
/** /**
* If the property is a promise, we will return a string that clearly states that it's a promise object, not a * If the property is a promise, we will return a string that clearly states that it's a promise object, not a
* normal object. If we don't do that, all promises will be serialized into "{}". * normal object. If we don't do that, all promises will be serialized into "{}".
*/ */
if (value instanceof Promise) { if (value instanceof Promise) {
// eslint-disable-next-line @typescript-eslint/no-base-to-string -- "[object Promise]" is exactly the string that we want.
return value.toString(); return value.toString();
} }
if (value instanceof Map) { if (value instanceof Map) {
@ -282,8 +283,8 @@ function argsToString(args: unknown[]): string {
})); }));
} }
return (out += `${nativeArg}`); return (out += String(nativeArg));
}, "") as string; }, "");
} }
function validateHGWOptions(ctx: NetscriptContext, opts: unknown): CompleteHGWOptions { function validateHGWOptions(ctx: NetscriptContext, opts: unknown): CompleteHGWOptions {
@ -296,6 +297,7 @@ function validateHGWOptions(ctx: NetscriptContext, opts: unknown): CompleteHGWOp
return result; return result;
} }
if (typeof opts !== "object") { if (typeof opts !== "object") {
// eslint-disable-next-line @typescript-eslint/no-base-to-string
throw errorMessage(ctx, `BasicHGWOptions must be an object if specified, was ${opts}`); throw errorMessage(ctx, `BasicHGWOptions must be an object if specified, was ${opts}`);
} }
// Safe assertion since threadOrOption type has been narrowed to a non-null object // Safe assertion since threadOrOption type has been narrowed to a non-null object
@ -723,7 +725,7 @@ function createPublicRunningScript(runningScript: RunningScript, workerScript?:
args: runningScript.args.slice(), args: runningScript.args.slice(),
dynamicRamUsage: workerScript && roundToTwo(workerScript.dynamicRamUsage), dynamicRamUsage: workerScript && roundToTwo(workerScript.dynamicRamUsage),
filename: runningScript.filename, filename: runningScript.filename,
logs: runningScript.logs.map((x) => "" + x), logs: runningScript.logs.map((x) => String(x)),
offlineExpGained: runningScript.offlineExpGained, offlineExpGained: runningScript.offlineExpGained,
offlineMoneyMade: runningScript.offlineMoneyMade, offlineMoneyMade: runningScript.offlineMoneyMade,
offlineRunningTime: runningScript.offlineRunningTime, offlineRunningTime: runningScript.offlineRunningTime,
@ -782,13 +784,21 @@ function validateBitNodeOptions(ctx: NetscriptContext, bitNodeOptions: unknown):
return result; return result;
} }
if (typeof bitNodeOptions !== "object") { if (typeof bitNodeOptions !== "object") {
// eslint-disable-next-line @typescript-eslint/no-base-to-string
throw errorMessage(ctx, `bitNodeOptions must be an object if it's specified. It was ${bitNodeOptions}.`); throw errorMessage(ctx, `bitNodeOptions must be an object if it's specified. It was ${bitNodeOptions}.`);
} }
const options = bitNodeOptions as Unknownify<BitNodeOptions>; const options = bitNodeOptions as Unknownify<BitNodeOptions>;
if (!(options.sourceFileOverrides instanceof Map)) { if (!(options.sourceFileOverrides instanceof Map)) {
throw errorMessage(ctx, `sourceFileOverrides must be a Map.`); throw errorMessage(ctx, `sourceFileOverrides must be a Map.`);
} }
const validationResultForSourceFileOverrides = validateSourceFileOverrides(options.sourceFileOverrides, true); const validationResultForSourceFileOverrides = validateSourceFileOverrides(
/**
* Cast the type from Map<any, any> to Map<number, number> to satisfy the lint rule. The validation logic in
* validateSourceFileOverrides will check the data.
*/
options.sourceFileOverrides as Map<number, number>,
true,
);
if (!validationResultForSourceFileOverrides.valid) { if (!validationResultForSourceFileOverrides.valid) {
throw errorMessage( throw errorMessage(
ctx, ctx,

@ -542,7 +542,7 @@ export const ns: InternalAPI<NSFull> = {
return [] as string[]; return [] as string[];
} }
return runningScriptObj.logs.map((x) => "" + x); return runningScriptObj.logs.map((x) => String(x));
}, },
tail: tail:
(ctx) => (ctx) =>
@ -1870,7 +1870,7 @@ function getFunctionNames(obj: object, prefix: string): string[] {
} else if (typeof value === "function") { } else if (typeof value === "function") {
functionNames.push(prefix + key); functionNames.push(prefix + key);
} else if (typeof value === "object") { } else if (typeof value === "object") {
functionNames.push(...getFunctionNames(value, `${prefix}${key}.`)); functionNames.push(...getFunctionNames(value as object, `${prefix}${key}.`));
} }
} }
return functionNames; return functionNames;

@ -1,4 +1,4 @@
import type { Singularity as ISingularity, Task as ITask } from "@nsdefs"; import type { Singularity as ISingularity } from "@nsdefs";
import { Player } from "@player"; import { Player } from "@player";
import { AugmentationName, CityName, FactionWorkType, GymType, LocationName, UniversityClassType } from "@enums"; import { AugmentationName, CityName, FactionWorkType, GymType, LocationName, UniversityClassType } from "@enums";
@ -1142,7 +1142,7 @@ export function NetscriptSingularity(): InternalAPI<ISingularity> {
? resolveScriptFilePath(helpers.string(ctx, "cbScript", _cbScript), ctx.workerScript.name) ? resolveScriptFilePath(helpers.string(ctx, "cbScript", _cbScript), ctx.workerScript.name)
: false; : false;
if (cbScript === null) { if (cbScript === null) {
throw helpers.errorMessage(ctx, `Could not resolve file path: ${_cbScript}`); throw helpers.errorMessage(ctx, `Could not resolve file path. callbackScript is null.`);
} }
enterBitNode(true, Player.bitNodeN, nextBN, helpers.validateBitNodeOptions(ctx, _bitNodeOptions)); enterBitNode(true, Player.bitNodeN, nextBN, helpers.validateBitNodeOptions(ctx, _bitNodeOptions));
if (cbScript) { if (cbScript) {
@ -1159,7 +1159,7 @@ export function NetscriptSingularity(): InternalAPI<ISingularity> {
? resolveScriptFilePath(helpers.string(ctx, "cbScript", _cbScript), ctx.workerScript.name) ? resolveScriptFilePath(helpers.string(ctx, "cbScript", _cbScript), ctx.workerScript.name)
: false; : false;
if (cbScript === null) { if (cbScript === null) {
throw helpers.errorMessage(ctx, `Could not resolve file path: ${_cbScript}`); throw helpers.errorMessage(ctx, `Could not resolve file path. callbackScript is null.`);
} }
const wd = GetServer(SpecialServers.WorldDaemon); const wd = GetServer(SpecialServers.WorldDaemon);
@ -1194,7 +1194,7 @@ export function NetscriptSingularity(): InternalAPI<ISingularity> {
getCurrentWork: (ctx) => () => { getCurrentWork: (ctx) => () => {
helpers.checkSingularityAccess(ctx); helpers.checkSingularityAccess(ctx);
if (!Player.currentWork) return null; if (!Player.currentWork) return null;
return Player.currentWork.APICopy() as ITask; return Player.currentWork.APICopy();
}, },
exportGame: (ctx) => () => { exportGame: (ctx) => () => {
helpers.checkSingularityAccess(ctx); helpers.checkSingularityAccess(ctx);

@ -30,7 +30,7 @@ function makeScriptBlob(code: string): Blob {
// config object to provide a hook point. // config object to provide a hook point.
export const config = { export const config = {
doImport(url: ScriptURL): Promise<ScriptModule> { doImport(url: ScriptURL): Promise<ScriptModule> {
return import(/*webpackIgnore:true*/ url); return import(/*webpackIgnore:true*/ url) as Promise<ScriptModule>;
}, },
}; };

@ -36,9 +36,9 @@ export function getGangFaction(this: PlayerObject): Faction {
return fac; return fac;
} }
export function getGangName(this: PlayerObject): string { export function getGangName(this: PlayerObject): FactionName | null {
const gang = this.gang; const gang = this.gang;
return gang ? gang.facName : ""; return gang ? gang.facName : null;
} }
export function hasGangWith(this: PlayerObject, facName: FactionName): boolean { export function hasGangWith(this: PlayerObject, facName: FactionName): boolean {

@ -302,7 +302,7 @@ export function applyForJob(
} }
if (!company.hasPosition(pos)) { if (!company.hasPosition(pos)) {
console.error(`Company ${company.name} does not have position ${pos}. Player.applyToCompany() failed.`); console.error(`Company ${company.name} does not have position ${pos.name}. Player.applyToCompany() failed.`);
return null; return null;
} }

@ -11,7 +11,7 @@ export function setPlayer(playerObj: PlayerObject): void {
} }
export function loadPlayer(saveString: string): PlayerObject { export function loadPlayer(saveString: string): PlayerObject {
const player = JSON.parse(saveString, Reviver); const player = JSON.parse(saveString, Reviver) as PlayerObject;
player.money = parseFloat(player.money + ""); player.money = parseFloat(player.money + "");
player.exploits = sanitizeExploits(player.exploits); player.exploits = sanitizeExploits(player.exploits);
return player; return player;

@ -18,7 +18,7 @@ function error(errorMsg: string, { id }: RFAMessage): RFAMessage {
return new RFAMessage({ error: errorMsg, id: id }); return new RFAMessage({ error: errorMsg, id: id });
} }
export const RFARequestHandler: Record<string, (message: RFAMessage) => void | RFAMessage> = { export const RFARequestHandler: Record<string, (message: RFAMessage) => RFAMessage> = {
pushFile: function (msg: RFAMessage): RFAMessage { pushFile: function (msg: RFAMessage): RFAMessage {
if (!isFileData(msg.params)) return error("Misses parameters", msg); if (!isFileData(msg.params)) return error("Misses parameters", msg);

@ -40,7 +40,7 @@ export class Remote {
} }
function handleMessageEvent(this: WebSocket, e: MessageEvent): void { function handleMessageEvent(this: WebSocket, e: MessageEvent): void {
const msg: RFAMessage = JSON.parse(e.data); const msg = JSON.parse(e.data as string) as RFAMessage;
if (!msg.method || !RFARequestHandler[msg.method]) { if (!msg.method || !RFARequestHandler[msg.method]) {
const response = new RFAMessage({ error: "Unknown message received", id: msg.id }); const response = new RFAMessage({ error: "Unknown message received", id: msg.id });

@ -44,7 +44,7 @@ import { isBinaryFormat } from "../electron/saveDataBinaryFormat";
import { downloadContentAsFile } from "./utils/FileUtils"; import { downloadContentAsFile } from "./utils/FileUtils";
import { showAPIBreaks } from "./utils/APIBreaks/APIBreak"; import { showAPIBreaks } from "./utils/APIBreaks/APIBreak";
import { breakInfos261 } from "./utils/APIBreaks/2.6.1"; import { breakInfos261 } from "./utils/APIBreaks/2.6.1";
import { handleGetSaveDataError } from "./Netscript/ErrorMessages"; import { handleGetSaveDataInfoError } from "./Netscript/ErrorMessages";
/* SaveObject.js /* SaveObject.js
* Defines the object used to save/load games * Defines the object used to save/load games
@ -130,7 +130,7 @@ class BitburnerSaveObject {
try { try {
saveData = await this.getSaveData(); saveData = await this.getSaveData();
} catch (error) { } catch (error) {
handleGetSaveDataError(error); handleGetSaveDataInfoError(error);
return; return;
} }
try { try {
@ -170,7 +170,7 @@ class BitburnerSaveObject {
try { try {
saveData = await this.getSaveData(); saveData = await this.getSaveData();
} catch (error) { } catch (error) {
handleGetSaveDataError(error); handleGetSaveDataInfoError(error);
return; return;
} }
const filename = this.getSaveFileName(); const filename = this.getSaveFileName();
@ -698,9 +698,12 @@ function evaluateVersionCompatibility(ver: string | number): void {
} catch (e) { } catch (e) {
anyExportsFailed = true; anyExportsFailed = true;
// We just need the text error, not a full stack trace // We just need the text error, not a full stack trace
console.error(`Failed to load export of material ${material.name} (${division.name} ${warehouse.city}) console.error(
`Failed to load export of material ${material.name} (${division.name} ${warehouse.city})
Original export details: ${JSON.stringify(originalExport)} Original export details: ${JSON.stringify(originalExport)}
Error: ${e}`); Error: ${e}`,
e,
);
} }
} }
} }
@ -805,16 +808,16 @@ async function loadGame(saveData: SaveData): Promise<boolean> {
if (Object.hasOwn(saveObj, "LastExportBonus")) { if (Object.hasOwn(saveObj, "LastExportBonus")) {
try { try {
ExportBonus.setLastExportBonus(JSON.parse(saveObj.LastExportBonus)); ExportBonus.setLastExportBonus(JSON.parse(saveObj.LastExportBonus));
} catch (err) { } catch (error) {
ExportBonus.setLastExportBonus(new Date().getTime()); ExportBonus.setLastExportBonus(new Date().getTime());
console.error("ERROR: Failed to parse last export bonus Settings " + err); console.error(`ERROR: Failed to parse last export bonus setting. Error: ${error}.`, error);
} }
} }
if (Player.gang && Object.hasOwn(saveObj, "AllGangsSave")) { if (Player.gang && Object.hasOwn(saveObj, "AllGangsSave")) {
try { try {
loadAllGangs(saveObj.AllGangsSave); loadAllGangs(saveObj.AllGangsSave);
} catch (e) { } catch (error) {
console.error("ERROR: Failed to parse AllGangsSave: " + e); console.error(`ERROR: Failed to parse AllGangsSave. Error: ${error}.`, error);
} }
} }
if (Object.hasOwn(saveObj, "VersionSave")) { if (Object.hasOwn(saveObj, "VersionSave")) {

@ -106,7 +106,7 @@ export class RunningScript {
let logEntry = txt; let logEntry = txt;
if (Settings.TimestampsFormat && typeof txt === "string") { if (Settings.TimestampsFormat && typeof txt === "string") {
logEntry = "[" + formatTime(Settings.TimestampsFormat) + "] " + logEntry; logEntry = `[${formatTime(Settings.TimestampsFormat)}] ${txt}`;
} }
this.logs.push(logEntry); this.logs.push(logEntry);

@ -14,6 +14,7 @@ import { NetscriptExtra } from "../NetscriptFunctions/Extra";
import * as enums from "../Enums"; import * as enums from "../Enums";
import { ns } from "../NetscriptFunctions"; import { ns } from "../NetscriptFunctions";
import { isLegacyScript } from "../Paths/ScriptFilePath"; import { isLegacyScript } from "../Paths/ScriptFilePath";
import { exceptionAlert } from "../utils/helpers/exceptionAlert";
/** Event emitter used for tracking when changes have been made to a content file. */ /** Event emitter used for tracking when changes have been made to a content file. */
export const fileEditEvents = new EventEmitter<[hostname: string, filename: ContentFilePath]>(); export const fileEditEvents = new EventEmitter<[hostname: string, filename: ContentFilePath]>();
@ -36,7 +37,9 @@ export class ScriptEditor {
for (const [apiKey, apiValue] of Object.entries(apiLayer)) { for (const [apiKey, apiValue] of Object.entries(apiLayer)) {
if (apiLayer === api && apiKey in hiddenAPI) continue; if (apiLayer === api && apiKey in hiddenAPI) continue;
apiKeys.push(apiKey); apiKeys.push(apiKey);
if (typeof apiValue === "object") populate(apiValue); if (typeof apiValue === "object") {
populate(apiValue as object);
}
} }
} }
populate(); populate();
@ -44,23 +47,25 @@ export class ScriptEditor {
(async function () { (async function () {
// We have to improve the default js language otherwise theme sucks // We have to improve the default js language otherwise theme sucks
const jsLanguage = monaco.languages.getLanguages().find((l) => l.id === "javascript"); const jsLanguage = monaco.languages.getLanguages().find((l) => l.id === "javascript");
// Unsupported function is not exposed in monaco public API. if (!jsLanguage) {
const l = await (jsLanguage as any).loader(); return;
}
const loader = await jsLanguage.loader();
// replaced the bare tokens with regexes surrounded by \b, e.g. \b{token}\b which matches a word-break on either side // replaced the bare tokens with regexes surrounded by \b, e.g. \b{token}\b which matches a word-break on either side
// this prevents the highlighter from highlighting pieces of variables that start with a reserved token name // this prevents the highlighter from highlighting pieces of variables that start with a reserved token name
l.language.tokenizer.root.unshift([new RegExp("\\bns\\b"), { token: "ns" }]); loader.language.tokenizer.root.unshift([new RegExp("\\bns\\b"), { token: "ns" }]);
for (const symbol of apiKeys) for (const symbol of apiKeys)
l.language.tokenizer.root.unshift([new RegExp(`\\b${symbol}\\b`), { token: "netscriptfunction" }]); loader.language.tokenizer.root.unshift([new RegExp(`\\b${symbol}\\b`), { token: "netscriptfunction" }]);
const otherKeywords = ["let", "const", "var", "function", "arguments"]; const otherKeywords = ["let", "const", "var", "function", "arguments"];
const otherKeyvars = ["true", "false", "null", "undefined"]; const otherKeyvars = ["true", "false", "null", "undefined"];
otherKeywords.forEach((k) => otherKeywords.forEach((k) =>
l.language.tokenizer.root.unshift([new RegExp(`\\b${k}\\b`), { token: "otherkeywords" }]), loader.language.tokenizer.root.unshift([new RegExp(`\\b${k}\\b`), { token: "otherkeywords" }]),
); );
otherKeyvars.forEach((k) => otherKeyvars.forEach((k) =>
l.language.tokenizer.root.unshift([new RegExp(`\\b${k}\\b`), { token: "otherkeyvars" }]), loader.language.tokenizer.root.unshift([new RegExp(`\\b${k}\\b`), { token: "otherkeyvars" }]),
); );
l.language.tokenizer.root.unshift([new RegExp("\\bthis\\b"), { token: "this" }]); loader.language.tokenizer.root.unshift([new RegExp("\\bthis\\b"), { token: "this" }]);
})(); })().catch((e) => exceptionAlert(e));
// Add ts definitions for API // Add ts definitions for API
const source = netscriptDefinitions.replace(/export /g, ""); const source = netscriptDefinitions.replace(/export /g, "");
@ -112,16 +117,20 @@ export class ScriptEditor {
// returns a reject promise if the language worker is not loaded yet, so we wait to // returns a reject promise if the language worker is not loaded yet, so we wait to
// call it until the language gets loaded. // call it until the language gets loaded.
const languageWorker = new Promise<(...uris: monaco.Uri[]) => unknown>((resolve) => const languageWorker = new Promise<(...uris: monaco.Uri[]) => unknown>((resolve) =>
monaco.languages.onLanguage(language, () => getLanguageWorker().then(resolve)), monaco.languages.onLanguage(language, () => {
getLanguageWorker()
.then(resolve)
.catch((error) => exceptionAlert(error));
}),
); );
// Whenever a model is created, arange for it synced to the language server. // Whenever a model is created, arrange for it to be synced to the language server.
monaco.editor.onDidCreateModel((model) => { monaco.editor.onDidCreateModel((model) => {
if (language === "typescript" && isLegacyScript(model.uri.path)) { if (language === "typescript" && isLegacyScript(model.uri.path)) {
// Don't sync legacy scripts to typescript worker. // Don't sync legacy scripts to typescript worker.
return; return;
} }
if (["javascript", "typescript"].includes(model.getLanguageId())) { if (["javascript", "typescript"].includes(model.getLanguageId())) {
languageWorker.then((cb) => cb(model.uri)); languageWorker.then((resolve) => resolve(model.uri)).catch((error) => exceptionAlert(error));
} }
}); });
} }

@ -33,6 +33,7 @@ import { useCallback } from "react";
import { type AST, getFileType, parseAST } from "../../utils/ScriptTransformer"; import { type AST, getFileType, parseAST } from "../../utils/ScriptTransformer";
import { RamCalculationErrorCode } from "../../Script/RamCalculationErrorCodes"; import { RamCalculationErrorCode } from "../../Script/RamCalculationErrorCodes";
import { hasScriptExtension, isLegacyScript } from "../../Paths/ScriptFilePath"; import { hasScriptExtension, isLegacyScript } from "../../Paths/ScriptFilePath";
import { exceptionAlert } from "../../utils/helpers/exceptionAlert";
interface IProps { interface IProps {
// Map of filename -> code // Map of filename -> code
@ -105,7 +106,9 @@ function Root(props: IProps): React.ReactElement {
const server = GetServer(currentScript.hostname); const server = GetServer(currentScript.hostname);
if (server === null) throw new Error("Server should not be null but it is."); if (server === null) throw new Error("Server should not be null but it is.");
server.writeToContentFile(currentScript.path, currentScript.code); server.writeToContentFile(currentScript.path, currentScript.code);
if (Settings.SaveGameOnFileSave) saveObject.saveGame(); if (Settings.SaveGameOnFileSave) {
saveObject.saveGame().catch((error) => exceptionAlert(error));
}
rerender(); rerender();
}, [rerender]); }, [rerender]);
@ -285,7 +288,9 @@ function Root(props: IProps): React.ReactElement {
if (!server) throw new Error("Server should not be null but it is."); if (!server) throw new Error("Server should not be null but it is.");
// This server helper already handles overwriting, etc. // This server helper already handles overwriting, etc.
server.writeToContentFile(scriptToSave.path, scriptToSave.code); server.writeToContentFile(scriptToSave.path, scriptToSave.code);
if (Settings.SaveGameOnFileSave) saveObject.saveGame(); if (Settings.SaveGameOnFileSave) {
saveObject.saveGame().catch((error) => exceptionAlert(error));
}
} }
function currentTabIndex(): number | undefined { function currentTabIndex(): number | undefined {

@ -35,7 +35,10 @@ export function Toolbar({ editor, onSave }: IProps) {
const [optionsOpen, { on: openOptions, off: closeOptions }] = useBoolean(false); const [optionsOpen, { on: openOptions, off: closeOptions }] = useBoolean(false);
function beautify(): void { function beautify(): void {
editor?.getAction("editor.action.formatDocument")?.run(); editor
?.getAction("editor.action.formatDocument")
?.run()
.catch((error) => console.error(error));
} }
const { ram, ramEntries, isUpdatingRAM, options, saveOptions } = useScriptEditorContext(); const { ram, ramEntries, isUpdatingRAM, options, saveOptions } = useScriptEditorContext();

@ -218,7 +218,7 @@ export function makeTheme(theme: IScriptEditorTheme): editor.IStandaloneThemeDat
return { base: theme.base, inherit: theme.inherit, rules: themeRules, colors: themeColors }; return { base: theme.base, inherit: theme.inherit, rules: themeRules, colors: themeColors };
} }
export async function loadThemes(defineTheme: DefineThemeFn): Promise<void> { export function loadThemes(defineTheme: DefineThemeFn): void {
defineTheme("monokai", { defineTheme("monokai", {
base: "vs-dark", base: "vs-dark",
inherit: true, inherit: true,

@ -157,7 +157,12 @@ export const Settings = {
disableSuffixes: false, disableSuffixes: false,
load(saveString: string) { load(saveString: string) {
const save = JSON.parse(saveString); const save = JSON.parse(saveString) as {
theme?: typeof Settings.theme;
styles?: typeof Settings.styles;
overview?: typeof Settings.overview;
EditorTheme?: typeof Settings.EditorTheme;
};
save.theme && Object.assign(Settings.theme, save.theme); save.theme && Object.assign(Settings.theme, save.theme);
save.styles && Object.assign(Settings.styles, save.styles); save.styles && Object.assign(Settings.styles, save.styles);
save.overview && Object.assign(Settings.overview, save.overview); save.overview && Object.assign(Settings.overview, save.overview);

@ -50,7 +50,7 @@ export function buyStock(
} }
if (stock == null || isNaN(shares)) { if (stock == null || isNaN(shares)) {
if (ctx) { if (ctx) {
helpers.log(ctx, () => `Invalid arguments: stock='${stock}' shares='${shares}'`); helpers.log(ctx, () => `Invalid arguments: stock='${stock?.name}' shares='${shares}'`);
} else if (opts.suppressDialog !== true) { } else if (opts.suppressDialog !== true) {
dialogBoxCreate("Failed to buy stock. This may be a bug, contact developer"); dialogBoxCreate("Failed to buy stock. This may be a bug, contact developer");
} }
@ -145,7 +145,7 @@ export function sellStock(
// Sanitize/Validate arguments // Sanitize/Validate arguments
if (stock == null || shares < 0 || isNaN(shares)) { if (stock == null || shares < 0 || isNaN(shares)) {
if (ctx) { if (ctx) {
helpers.log(ctx, () => `Invalid arguments: stock='${stock}' shares='${shares}'`); helpers.log(ctx, () => `Invalid arguments: stock='${stock?.name}' shares='${shares}'`);
} else if (opts.suppressDialog !== true) { } else if (opts.suppressDialog !== true) {
dialogBoxCreate( dialogBoxCreate(
"Failed to sell stock. This is probably due to an invalid quantity. Otherwise, this may be a bug, contact developer", "Failed to sell stock. This is probably due to an invalid quantity. Otherwise, this may be a bug, contact developer",
@ -225,7 +225,7 @@ export function shortStock(
} }
if (stock == null || isNaN(shares)) { if (stock == null || isNaN(shares)) {
if (ctx) { if (ctx) {
helpers.log(ctx, () => `Invalid arguments: stock='${stock}' shares='${shares}'`); helpers.log(ctx, () => `Invalid arguments: stock='${stock?.name}' shares='${shares}'`);
} else if (opts.suppressDialog !== true) { } else if (opts.suppressDialog !== true) {
dialogBoxCreate( dialogBoxCreate(
"Failed to initiate a short position in a stock. This is probably " + "Failed to initiate a short position in a stock. This is probably " +
@ -321,7 +321,7 @@ export function sellShort(
): boolean { ): boolean {
if (stock == null || isNaN(shares) || shares < 0) { if (stock == null || isNaN(shares) || shares < 0) {
if (ctx) { if (ctx) {
helpers.log(ctx, () => `Invalid arguments: stock='${stock}' shares='${shares}'`); helpers.log(ctx, () => `Invalid arguments: stock='${stock?.name}' shares='${shares}'`);
} else if (!opts.suppressDialog) { } else if (!opts.suppressDialog) {
dialogBoxCreate( dialogBoxCreate(
"Failed to sell a short position in a stock. This is probably " + "Failed to sell a short position in a stock. This is probably " +

@ -20,7 +20,13 @@ export function exportScripts(pattern: string, server: BaseServer, currDir = roo
const filename = `bitburner${ const filename = `bitburner${
hasScriptExtension(pattern) ? "Scripts" : pattern.endsWith(".txt") ? "Texts" : "Files" hasScriptExtension(pattern) ? "Scripts" : pattern.endsWith(".txt") ? "Texts" : "Files"
}.zip`; }.zip`;
zip.generateAsync({ type: "blob" }).then((content: Blob) => downloadContentAsFile(content, filename)); zip
.generateAsync({ type: "blob" })
.then((content: Blob) => downloadContentAsFile(content, filename))
.catch((error) => {
console.error(error);
Terminal.error(`Cannot compress scripts with pattern ${pattern} on ${server.hostname}. Error: ${error}`);
});
} }
export function download(args: (string | number | boolean)[], server: BaseServer): void { export function download(args: (string | number | boolean)[], server: BaseServer): void {
@ -33,9 +39,10 @@ export function download(args: (string | number | boolean)[], server: BaseServer
try { try {
exportScripts(pattern, server, Terminal.currDir); exportScripts(pattern, server, Terminal.currDir);
return; return;
} catch (e: any) { } catch (error) {
const msg = String(e?.message ?? e); console.error(error);
return Terminal.error(msg); Terminal.error(`Cannot export scripts with pattern ${pattern} on ${server.hostname}. Error: ${error}`);
return;
} }
} }
const path = Terminal.getFilepath(pattern); const path = Terminal.getFilepath(pattern);

@ -383,13 +383,19 @@ function writeToTerminal(
if (options.isVerbose) Terminal.print(verboseInfo); if (options.isVerbose) Terminal.print(verboseInfo);
} }
function checkOutFile(outFileStr: string, options: Options, server: BaseServer): ContentFilePath | void { function checkOutFile(outFileStr: string, options: Options, server: BaseServer): ContentFilePath | null {
if (!outFileStr) return; if (!outFileStr) {
return null;
}
const outFilePath = Terminal.getFilepath(outFileStr); const outFilePath = Terminal.getFilepath(outFileStr);
if (!outFilePath || !hasTextExtension(outFilePath)) { if (!outFilePath || !hasTextExtension(outFilePath)) {
return Terminal.error(ERR.badOutFile(outFileStr)); Terminal.error(ERR.badOutFile(outFileStr));
return null;
}
if (!options.isOverWrite && server.textFiles.has(outFilePath)) {
Terminal.error(ERR.outFileExists(outFileStr));
return null;
} }
if (!options.isOverWrite && server.textFiles.has(outFilePath)) return Terminal.error(ERR.outFileExists(outFileStr));
return outFilePath; return outFilePath;
} }
@ -433,7 +439,8 @@ export function grep(args: (string | number | boolean)[], server: BaseServer): v
if (options.isPipeIn) files.length = 0; if (options.isPipeIn) files.length = 0;
if (!options.isQuiet) writeToTerminal(prettyResult, options, results, files, pattern); if (!options.isQuiet) writeToTerminal(prettyResult, options, results, files, pattern);
if (params.outfile && outFilePath) server.writeToContentFile(outFilePath, rawResult.join("\n")); if (params.outfile && outFilePath) server.writeToContentFile(outFilePath, rawResult.join("\n"));
} catch (e) { } catch (error) {
Terminal.error("grep processing error: " + e); console.error(error);
Terminal.error(`grep processing error: ${error}`);
} }
} }

@ -44,7 +44,8 @@ export function kill(args: (string | number | boolean)[], server: BaseServer): v
if (killed >= 5) { if (killed >= 5) {
Terminal.print(`... killed ${killed} instances total`); Terminal.print(`... killed ${killed} instances total`);
} }
} catch (e) { } catch (error) {
Terminal.error(e + ""); console.error(error);
Terminal.error(String(error));
} }
} }

@ -43,7 +43,8 @@ export function mem(args: (string | number | boolean)[], server: BaseServer): vo
// Let's warn the user that he might need to save his script again to generate the detailed entries // Let's warn the user that he might need to save his script again to generate the detailed entries
Terminal.warn("You might have to open & save this script to see the detailed RAM usage information."); Terminal.warn("You might have to open & save this script to see the detailed RAM usage information.");
} }
} catch (e) { } catch (error) {
Terminal.error(e + ""); console.error(error);
Terminal.error(String(error));
} }
} }

@ -19,7 +19,10 @@ export function run(args: (string | number | boolean)[], server: BaseServer): vo
if (hasScriptExtension(path)) { if (hasScriptExtension(path)) {
return runScript(path, args, server); return runScript(path, args, server);
} else if (hasContractExtension(path)) { } else if (hasContractExtension(path)) {
Terminal.runContract(path); Terminal.runContract(path).catch((error) => {
console.error(error);
Terminal.error(`Cannot run contract ${path} on ${server.hostname}. Error: ${error}.`);
});
return; return;
} else if (hasProgramExtension(path)) { } else if (hasProgramExtension(path)) {
return runProgram(path, args, server); return runProgram(path, args, server);

@ -25,7 +25,7 @@ export function runScript(path: ScriptFilePath, commandArgs: (string | number |
argv: commandArgs, argv: commandArgs,
}); });
} catch (error) { } catch (error) {
Terminal.error(`Invalid arguments. ${String(error)}.`); Terminal.error(`Invalid arguments. ${error}.`);
return; return;
} }
const tailFlag = flags["--tail"] === true; const tailFlag = flags["--tail"] === true;

@ -32,7 +32,8 @@ export function tail(commandArray: (string | number | boolean)[], server: BaseSe
} }
LogBoxEvents.emit(runningScript); LogBoxEvents.emit(runningScript);
} }
} catch (e) { } catch (error) {
Terminal.error(e + ""); console.error(error);
Terminal.error(String(error));
} }
} }

@ -37,7 +37,7 @@ function getDB(): Promise<IDBObjectStore> {
export function load(): Promise<SaveData> { export function load(): Promise<SaveData> {
return getDB().then((db) => { return getDB().then((db) => {
return new Promise<SaveData>((resolve, reject) => { return new Promise<SaveData>((resolve, reject) => {
const request: IDBRequest<SaveData> = db.get("save"); const request = db.get("save") as IDBRequest<SaveData>;
request.onerror = function (this: IDBRequest<SaveData>) { request.onerror = function (this: IDBRequest<SaveData>) {
reject(new Error("Error in Database request to get save data", { cause: this.error })); reject(new Error("Error in Database request to get save data", { cause: this.error }));
}; };

@ -237,7 +237,7 @@ const Engine: {
Engine.Counters.autoSaveCounter = 60 * 5; // Let's check back in a bit Engine.Counters.autoSaveCounter = 60 * 5; // Let's check back in a bit
} else { } else {
Engine.Counters.autoSaveCounter = Settings.AutosaveInterval * 5; Engine.Counters.autoSaveCounter = Settings.AutosaveInterval * 5;
saveObject.saveGame(!Settings.SuppressSavedGameToast); saveObject.saveGame(!Settings.SuppressSavedGameToast).catch((error) => console.error(error));
} }
} }
}, },

@ -75,6 +75,7 @@ import { HistoryProvider } from "./React/Documentation";
import { GoRoot } from "../Go/ui/GoRoot"; import { GoRoot } from "../Go/ui/GoRoot";
import { Settings } from "../Settings/Settings"; import { Settings } from "../Settings/Settings";
import { isBitNodeFinished } from "../BitNode/BitNodeUtils"; import { isBitNodeFinished } from "../BitNode/BitNodeUtils";
import { exceptionAlert } from "../utils/helpers/exceptionAlert";
const htmlLocation = location; const htmlLocation = location;
@ -154,8 +155,14 @@ export function GameRoot(): React.ReactElement {
for (const server of GetAllServers()) { for (const server of GetAllServers()) {
server.runningScriptMap.clear(); server.runningScriptMap.clear();
} }
saveObject.saveGame(); saveObject
.saveGame()
.then(() => {
setTimeout(() => htmlLocation.reload(), 2000); setTimeout(() => htmlLocation.reload(), 2000);
})
.catch((error) => {
exceptionAlert(error);
});
} }
function attemptedForbiddenRouting(name: string) { function attemptedForbiddenRouting(name: string) {
@ -333,11 +340,13 @@ export function GameRoot(): React.ReactElement {
case Page.Options: { case Page.Options: {
mainPage = ( mainPage = (
<GameOptionsRoot <GameOptionsRoot
save={() => saveObject.saveGame()} save={() => {
saveObject.saveGame().catch((error) => exceptionAlert(error));
}}
export={() => { export={() => {
// Apply the export bonus before saving the game // Apply the export bonus before saving the game
onExport(); onExport();
saveObject.exportGame(); saveObject.exportGame().catch((error) => exceptionAlert(error));
}} }}
forceKill={killAllScripts} forceKill={killAllScripts}
softReset={softReset} softReset={softReset}
@ -356,7 +365,7 @@ export function GameRoot(): React.ReactElement {
exportGameFn={() => { exportGameFn={() => {
// Apply the export bonus before saving the game // Apply the export bonus before saving the game
onExport(); onExport();
saveObject.exportGame(); saveObject.exportGame().catch((error) => exceptionAlert(error));
}} }}
installAugmentationsFn={() => { installAugmentationsFn={() => {
installAugmentations(); installAugmentations();
@ -395,7 +404,9 @@ export function GameRoot(): React.ReactElement {
!ITutorial.isRunning ? ( !ITutorial.isRunning ? (
<CharacterOverview <CharacterOverview
parentOpen={parentOpen} parentOpen={parentOpen}
save={() => saveObject.saveGame()} save={() => {
saveObject.saveGame().catch((error) => exceptionAlert(error));
}}
killScripts={killAllScripts} killScripts={killAllScripts}
/> />
) : ( ) : (

@ -38,7 +38,7 @@ import { useBoolean } from "../hooks";
import { ComparisonIcon } from "./ComparisonIcon"; import { ComparisonIcon } from "./ComparisonIcon";
import { SaveData } from "../../../types"; import { SaveData } from "../../../types";
import { handleGetSaveDataError } from "../../../Netscript/ErrorMessages"; import { handleGetSaveDataInfoError } from "../../../Netscript/ErrorMessages";
const useStyles = makeStyles()((theme: Theme) => ({ const useStyles = makeStyles()((theme: Theme) => ({
root: { root: {
@ -140,7 +140,7 @@ export const ImportSave = (props: { saveData: SaveData; automatic: boolean }): J
// We cannot show dialog box in this screen (due to "withPopups = false"), so we will try showing it with a // We cannot show dialog box in this screen (due to "withPopups = false"), so we will try showing it with a
// delay. 1 second is usually enough to go back to other normal screens that allow showing popups. // delay. 1 second is usually enough to go back to other normal screens that allow showing popups.
setTimeout(() => { setTimeout(() => {
handleGetSaveDataError(error); handleGetSaveDataInfoError(error);
}, 1000); }, 1000);
}); });
} }

@ -1,8 +1,6 @@
/** Generic Event Emitter class following a subscribe/publish paradigm. */ /** Generic Event Emitter class following a subscribe/publish paradigm. */
export class EventEmitter<T extends any[]> { export class EventEmitter<T extends any[]> {
private subscribers: Set<(...args: [...T]) => void | undefined> = new Set(); private subscribers: Set<(...args: [...T]) => void> = new Set();
constructor() {}
subscribe(s: (...args: [...T]) => void): () => void { subscribe(s: (...args: [...T]) => void): () => void {
this.subscribers.add(s); this.subscribers.add(s);

@ -239,7 +239,7 @@ export const v2APIBreak = () => {
for (const server of GetAllServers()) { for (const server of GetAllServers()) {
server.runningScriptMap = new Map(); server.runningScriptMap = new Map();
} }
saveObject.exportGame(); saveObject.exportGame().catch((e) => console.error(e));
}; };
const formatOffenders = (offenders: IFileLine[]): string => { const formatOffenders = (offenders: IFileLine[]): string => {