From e2e9b084bcbb50de52108237845284177a6069b8 Mon Sep 17 00:00:00 2001 From: David Walker Date: Mon, 8 May 2023 21:13:05 -0700 Subject: [PATCH] SETTINGS: Add an autoexec setting (#505) --- src/GameOptions/ui/AutoexecInput.tsx | 106 +++++++++++++++++++++++++++ src/GameOptions/ui/SystemPage.tsx | 14 +++- src/NetscriptWorker.ts | 57 ++++++++++++-- src/Settings/Settings.ts | 2 + 4 files changed, 173 insertions(+), 6 deletions(-) create mode 100644 src/GameOptions/ui/AutoexecInput.tsx diff --git a/src/GameOptions/ui/AutoexecInput.tsx b/src/GameOptions/ui/AutoexecInput.tsx new file mode 100644 index 000000000..a438f7faf --- /dev/null +++ b/src/GameOptions/ui/AutoexecInput.tsx @@ -0,0 +1,106 @@ +import React, { useState } from "react"; +import { Box, InputAdornment, TextField, Tooltip, Typography } from "@mui/material"; +import { Settings } from "../../Settings/Settings"; +import { parseCommand } from "../../Terminal/Parser"; +import { resolveScriptFilePath } from "../../Paths/ScriptFilePath"; +import { formatRam } from "../../ui/formatNumber"; +import CheckCircleIcon from "@mui/icons-material/CheckCircle"; +import WarningIcon from "@mui/icons-material/Warning"; +import ErrorIcon from "@mui/icons-material/Error"; +import { Player } from "@player"; + +interface IProps { + tooltip: React.ReactElement; + label: string; +} + +export const AutoexecInput = (props: IProps): React.ReactElement => { + const [autoexec, setAutoexec] = useState(Settings.AutoexecScript); + + function handleAutoexecChange(event: React.ChangeEvent): void { + Settings.AutoexecScript = event.target.value; + setAutoexec(event.target.value); + } + + // None of these errors block the saving of the setting; what is invalid now + // could become valid later. + function createTooltip() { + const args = parseCommand(autoexec); + if (args.length === 0) { + return ( + No script will be auto-launched}> + + + ); + } + const cmd = String(args[0]); + const scriptPath = resolveScriptFilePath(cmd); + if (!scriptPath) { + return ( + "{cmd}" is invalid for a script name (maybe missing suffix?)}> + + + ); + } + const home = Player.getHomeComputer(); + const script = home.scripts.get(scriptPath); + if (!script) { + return ( + {cmd} does not exist!}> + + + ); + } + const ramUsage = script.getRamUsage(home.scripts); + if (ramUsage === null) { + return ( + {cmd} has errors!}> + + + ); + } + // Stolen from Prestige.ts + const minRam = Player.sourceFileLvl(9) >= 2 ? 128 : Player.sourceFileLvl(1) > 0 ? 32 : 8; + if (ramUsage <= minRam) { + return ( + + {cmd} costs {formatRam(ramUsage)} + + } + > + + + ); + } else { + return ( + + {cmd} costs {formatRam(ramUsage)}, you might only have {formatRam(minRam)} on home! + + } + > + + + ); + } + } + + return ( + + {props.tooltip}}> + {props.label} + + {createTooltip()}, + }} + value={autoexec} + onChange={handleAutoexecChange} + /> + + ); +}; diff --git a/src/GameOptions/ui/SystemPage.tsx b/src/GameOptions/ui/SystemPage.tsx index 7af32f8cd..075f74584 100644 --- a/src/GameOptions/ui/SystemPage.tsx +++ b/src/GameOptions/ui/SystemPage.tsx @@ -2,6 +2,7 @@ import React, { useState } from "react"; import { Settings } from "../../Settings/Settings"; import { GameOptionsPage } from "./GameOptionsPage"; import { OptionsSlider } from "./OptionsSlider"; +import { AutoexecInput } from "./AutoexecInput"; import { OptionSwitch } from "../../ui/React/OptionSwitch"; export const SystemPage = (): React.ReactElement => { @@ -45,6 +46,16 @@ export const SystemPage = (): React.ReactElement => { {/* Wrap in a React fragment to prevent the sliders from breaking as list items */} <> + + Path to a script (with optional args) to run on game load. The script will be run on home, launched before + any saved running scripts. It will have the "temporary" setting, so if it stays running it won't be saved. + + } + /> +
{ tooltip={ <> If this is set, the save file will exclude all running scripts. This is only useful if your save is lagging - a lot. You'll have to restart your script every time you launch the game. + a lot. You'll have to restart your script every time you launch the game, possibly by using the "autoexec" + option. } /> diff --git a/src/NetscriptWorker.ts b/src/NetscriptWorker.ts index f5e2eae18..8b1645426 100644 --- a/src/NetscriptWorker.ts +++ b/src/NetscriptWorker.ts @@ -23,11 +23,13 @@ import { Settings } from "./Settings/Settings"; import { generate } from "escodegen"; import { dialogBoxCreate } from "./ui/React/DialogBox"; +import { formatRam } from "./ui/formatNumber"; import { arrayToString } from "./utils/helpers/ArrayHelpers"; import { roundToTwo } from "./utils/helpers/roundToTwo"; import { parse } from "acorn"; import { simple as walksimple } from "acorn-walk"; +import { parseCommand } from "./Terminal/Parser"; import { Terminal } from "./Terminal"; import { ScriptArg } from "@nsdefs"; import { handleUnknownError, CompleteRunOptions, getRunningScriptsByArgs } from "./Netscript/NetscriptHelpers"; @@ -284,10 +286,12 @@ function createAndAddWorkerScript(runningScriptObj: RunningScript, server: BaseS const ramAvailable = server.maxRam - server.ramUsed; // Check failure conditions before generating the workersScript and return false if (ramUsage > ramAvailable + 0.001) { - dialogBoxCreate( - `Not enough RAM to run script ${runningScriptObj.filename} with args ${arrayToString(runningScriptObj.args)}.\n` + - `This can occur when you reload the game and the script's RAM usage has increased (either because of an update to the game or ` + - `your changes to the script).\nThis can also occur if you have attempted to launch a script from a tail window with insufficient RAM. `, + deferredError( + `Not enough RAM to run script ${runningScriptObj.filename} with args ${arrayToString( + runningScriptObj.args, + )}, needed ${formatRam(ramUsage)} but only have ${formatRam(ramAvailable)} free +If you are seeing this on startup, likely causes are that the autoexec script is too big to fit in RAM, or it took up too much space and other previously running scripts couldn't fit on home. +Otherwise, this can also occur if you have attempted to launch a script from a tail window with insufficient RAM.`, ); return false; } @@ -295,7 +299,7 @@ function createAndAddWorkerScript(runningScriptObj: RunningScript, server: BaseS // Get the pid const pid = generateNextPid(); if (pid === -1) { - dialogBoxCreate( + deferredError( `Failed to start script because could not find available PID. This is most ` + `because you have too many scripts running.`, ); @@ -340,6 +344,42 @@ export function updateOnlineScriptTimes(numCycles = 1): void { } } +// Needed for popping dialog boxes in functions that run *before* the UI is +// created, and thus before AlertManager exists to listen to the alerts we +// create. +function deferredError(msg: string) { + setTimeout(() => dialogBoxCreate(msg), 0); +} + +function createAutoexec(server: BaseServer): RunningScript | null { + const args = parseCommand(Settings.AutoexecScript); + if (args.length === 0) return null; + + const cmd = String(args[0]); + const scriptPath = resolveScriptFilePath(cmd); + if (!scriptPath) { + deferredError(`While running autoexec script: +"${cmd}" is invalid for a script name (maybe missing suffix?)`); + return null; + } + const script = server.scripts.get(scriptPath); + if (!script) { + deferredError(`While running autoexec script: +"${cmd}" does not exist!`); + return null; + } + const ramUsage = script.getRamUsage(server.scripts); + if (ramUsage === null) { + deferredError(`While running autoexec script: +"${cmd}" has errors!`); + return null; + } + args.shift(); + const rs = new RunningScript(script, ramUsage, args); + rs.temporary = true; + return rs; +} + /** * Called when the game is loaded. Loads all running scripts (from all servers) * into worker scripts so that they will start running @@ -360,6 +400,13 @@ export function loadAllRunningScripts(): void { // Start game with no scripts continue; } + if (server.hostname === "home") { + // Push autoexec script onto the front of the list + const runningScript = createAutoexec(server); + if (runningScript) { + rsList.unshift(runningScript); + } + } for (const runningScript of rsList) { startWorkerScript(runningScript, server); scriptCalculateOfflineProduction(runningScript); diff --git a/src/Settings/Settings.ts b/src/Settings/Settings.ts index 693db595a..15ab27e99 100644 --- a/src/Settings/Settings.ts +++ b/src/Settings/Settings.ts @@ -10,6 +10,8 @@ export const Settings = { ActiveScriptsServerPageSize: 10, /** How many scripts per page */ ActiveScriptsScriptPageSize: 10, + /** Script + args to launch on game load */ + AutoexecScript: "", /** How often the game should autosave the player's progress, in seconds. */ AutosaveInterval: 60, /** How many milliseconds between execution points for Netscript 1 statements. */