Add ns.getRecentScripts()

This commit is contained in:
smolgumball 2022-01-19 00:04:48 -07:00
parent 53727f6222
commit 74e2acfa6f
8 changed files with 184 additions and 51 deletions

@ -135,6 +135,7 @@ export const RamCosts: IMap<any> = {
scp: RamCostConstants.ScriptScpRamCost, scp: RamCostConstants.ScriptScpRamCost,
ls: RamCostConstants.ScriptScanRamCost, ls: RamCostConstants.ScriptScanRamCost,
ps: RamCostConstants.ScriptScanRamCost, ps: RamCostConstants.ScriptScanRamCost,
getRecentScripts: RamCostConstants.ScriptScanRamCost,
hasRootAccess: RamCostConstants.ScriptHasRootAccessRamCost, hasRootAccess: RamCostConstants.ScriptHasRootAccessRamCost,
getIp: RamCostConstants.ScriptGetHostnameRamCost, getIp: RamCostConstants.ScriptGetHostnameRamCost,
getHostname: RamCostConstants.ScriptGetHostnameRamCost, getHostname: RamCostConstants.ScriptGetHostnameRamCost,

@ -1,19 +1,23 @@
import { RunningScript } from "src/Script/RunningScript"; import { RunningScript } from "src/Script/RunningScript";
import { Settings } from "../Settings/Settings";
import { WorkerScript } from "./WorkerScript"; import { WorkerScript } from "./WorkerScript";
export const recentScripts: RecentScript[] = []; export const recentScripts: RecentScript[] = [];
export function AddRecentScript(workerScript: WorkerScript): void { export function AddRecentScript(workerScript: WorkerScript): void {
if (recentScripts.find((r) => r.pid === workerScript.pid)) return; if (recentScripts.find((r) => r.pid === workerScript.pid)) return;
const killedTime = new Date();
recentScripts.unshift({ recentScripts.unshift({
filename: workerScript.name, filename: workerScript.name,
args: workerScript.args, args: workerScript.args,
pid: workerScript.pid, pid: workerScript.pid,
timestamp: new Date(), timestamp: killedTime,
timestampEpoch: killedTime.getTime(),
runningScript: workerScript.scriptRef, runningScript: workerScript.scriptRef,
}); });
while (recentScripts.length > 50) {
while (recentScripts.length > Settings.MaxRecentScriptsCapacity) {
recentScripts.pop(); recentScripts.pop();
} }
} }
@ -23,5 +27,6 @@ export interface RecentScript {
args: string[]; args: string[];
pid: number; pid: number;
timestamp: Date; timestamp: Date;
timestampEpoch: number;
runningScript: RunningScript; runningScript: RunningScript;
} }

@ -78,6 +78,8 @@ import {
Gang as IGang, Gang as IGang,
Bladeburner as IBladeburner, Bladeburner as IBladeburner,
Stanek as IStanek, Stanek as IStanek,
RunningScript as IRunningScript,
RecentScript as IRecentScript,
SourceFileLvl, SourceFileLvl,
} from "./ScriptEditor/NetscriptDefinitions"; } from "./ScriptEditor/NetscriptDefinitions";
import { NetscriptSingularity } from "./NetscriptFunctions/Singularity"; import { NetscriptSingularity } from "./NetscriptFunctions/Singularity";
@ -90,6 +92,7 @@ import { SnackbarEvents } from "./ui/React/Snackbar";
import { Flags } from "./NetscriptFunctions/Flags"; import { Flags } from "./NetscriptFunctions/Flags";
import { calculateIntelligenceBonus } from "./PersonObjects/formulas/intelligence"; import { calculateIntelligenceBonus } from "./PersonObjects/formulas/intelligence";
import { CalculateShareMult, StartSharing } from "./NetworkShare/Share"; import { CalculateShareMult, StartSharing } from "./NetworkShare/Share";
import { recentScripts } from "./Netscript/RecentScripts";
interface NS extends INS { interface NS extends INS {
[key: string]: any; [key: string]: any;
@ -204,6 +207,32 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
return null; return null;
}; };
/**
* Sanitizes a `RunningScript` to remove sensitive information, making it suitable for
* return through an NS function.
* @see NS.getRecentScripts
* @see NS.getRunningScript
* @param runningScript Existing, internal RunningScript
* @returns A sanitized, NS-facing copy of the RunningScript
*/
const createPublicRunningScript = function (runningScript: RunningScript): IRunningScript {
return {
args: runningScript.args.slice(),
filename: runningScript.filename,
logs: runningScript.logs.slice(),
offlineExpGained: runningScript.offlineExpGained,
offlineMoneyMade: runningScript.offlineMoneyMade,
offlineRunningTime: runningScript.offlineRunningTime,
onlineExpGained: runningScript.onlineExpGained,
onlineMoneyMade: runningScript.onlineMoneyMade,
onlineRunningTime: runningScript.onlineRunningTime,
pid: runningScript.pid,
ramUsage: runningScript.ramUsage,
server: runningScript.server,
threads: runningScript.threads,
};
};
/** /**
* Helper function for getting the error log message when the user specifies * Helper function for getting the error log message when the user specifies
* a nonexistent running script * a nonexistent running script
@ -1310,6 +1339,10 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
allFiles.sort(); allFiles.sort();
return allFiles; return allFiles;
}, },
getRecentScripts: function (): IRecentScript[] {
updateDynamicRam("getRecentScripts", getRamCost(Player, "getRecentScripts"));
return recentScripts.map((rs) => ({ ...rs, runningScript: createPublicRunningScript(rs.runningScript) }));
},
ps: function (hostname: any = workerScript.hostname): any { ps: function (hostname: any = workerScript.hostname): any {
updateDynamicRam("ps", getRamCost(Player, "ps")); updateDynamicRam("ps", getRamCost(Player, "ps"));
const server = safeGetServer(hostname, "ps"); const server = safeGetServer(hostname, "ps");
@ -1974,21 +2007,7 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
runningScript = getRunningScript(fn, hostname, "getRunningScript", args); runningScript = getRunningScript(fn, hostname, "getRunningScript", args);
} }
if (runningScript === null) return null; if (runningScript === null) return null;
return { return createPublicRunningScript(runningScript);
args: runningScript.args.slice(),
filename: runningScript.filename,
logs: runningScript.logs.slice(),
offlineExpGained: runningScript.offlineExpGained,
offlineMoneyMade: runningScript.offlineMoneyMade,
offlineRunningTime: runningScript.offlineRunningTime,
onlineExpGained: runningScript.onlineExpGained,
onlineMoneyMade: runningScript.onlineMoneyMade,
onlineRunningTime: runningScript.onlineRunningTime,
pid: runningScript.pid,
ramUsage: runningScript.ramUsage,
server: runningScript.server,
threads: runningScript.threads,
};
}, },
getHackTime: function (hostname: any): any { getHackTime: function (hostname: any): any {
updateDynamicRam("getHackTime", getRamCost(Player, "getHackTime")); updateDynamicRam("getHackTime", getRamCost(Player, "getHackTime"));

@ -99,22 +99,56 @@ interface Player {
/** /**
* @public * @public
*/ */
interface RunningScript { export interface RunningScript {
/** Arguments the script was called with */
args: string[]; args: string[];
/** Filename of the script */
filename: string; filename: string;
/**
* Script logs as an array. The newest log entries are at the bottom.
* Timestamps, if enabled, are placed inside `[brackets]` at the start of each line.
**/
logs: string[]; logs: string[];
/** Total amount of hacking experience earned from this script when offline */
offlineExpGained: number; offlineExpGained: number;
/** Total amount of money made by this script when offline */
offlineMoneyMade: number; offlineMoneyMade: number;
/** Number of seconds that the script has been running offline */
offlineRunningTime: number; offlineRunningTime: number;
/** Total amount of hacking experience earned from this script when online */
onlineExpGained: number; onlineExpGained: number;
/** Total amount of money made by this script when online */
onlineMoneyMade: number; onlineMoneyMade: number;
/** Number of seconds that this script has been running online */
onlineRunningTime: number; onlineRunningTime: number;
/** Process ID. Must be an integer */
pid: number; pid: number;
/** How much RAM this script uses for ONE thread */
ramUsage: number; ramUsage: number;
/** Hostname of the server on which this script runs */
server: string; server: string;
/** Number of threads that this script runs with */
threads: number; threads: number;
} }
/**
* @public
*/
interface RecentScript {
/** Arguments the script was called with */
args: string[];
/** Filename of the script */
filename: string;
/** Process ID. Must be an integer */
pid: number;
/** Timestamp of when the script was killed */
timestamp: Date;
/** Numeric epoch of timestamp */
timestampEpoch: number;
/** An inactive copy of the last `RunningScript` associated to the script */
runningScript: RunningScript;
}
/** /**
* Data representing the internal values of a crime. * Data representing the internal values of a crime.
* @public * @public
@ -4567,6 +4601,26 @@ export interface NS extends Singularity {
*/ */
getScriptLogs(fn?: string, host?: string, ...args: any[]): string[]; getScriptLogs(fn?: string, host?: string, ...args: any[]): string[];
/**
* Get an array of recently killed scripts across all servers.
* @remarks
* RAM cost: 0.2 GB
*
* The most recently killed script is the first element in the array.
* Note that there is a maximum number of recently killed scripts which are tracked.
* This is configurable in the game's options as `Recently killed scripts size`.
*
* @usage below:
* ```ts
* let recentScripts = ns.getRecentScripts();
* let mostRcent = recentScripts.pop()
* ns.tprint(mostRecent.logs.join('\n'))
* ```
*
* @returns Array with information about previously killed scripts.
*/
getRecentScripts(): RecentScript[];
/** /**
* Open the tail window of a script. * Open the tail window of a script.
* @remarks * @remarks

@ -131,13 +131,12 @@ export function Root(props: IProps): React.ReactElement {
// Prevent Crash if script is open on deleted server // Prevent Crash if script is open on deleted server
openScripts = openScripts.filter((script) => { openScripts = openScripts.filter((script) => {
return GetServer(script.hostname) !== null; return GetServer(script.hostname) !== null;
}) });
if (currentScript && (GetServer(currentScript.hostname) === null)) { if (currentScript && GetServer(currentScript.hostname) === null) {
currentScript = openScripts[0]; currentScript = openScripts[0];
if (currentScript === undefined) currentScript = null; if (currentScript === undefined) currentScript = null;
} }
const [dimensions, setDimensions] = useState({ const [dimensions, setDimensions] = useState({
height: window.innerHeight, height: window.innerHeight,
width: window.innerWidth, width: window.innerWidth,
@ -205,7 +204,7 @@ export function Root(props: IProps): React.ReactElement {
}); });
editor.focus(); editor.focus();
}); });
} catch { } } catch {}
} else if (!options.vim) { } else if (!options.vim) {
// Whem vim mode is disabled // Whem vim mode is disabled
vimEditor?.dispose(); vimEditor?.dispose();
@ -314,13 +313,18 @@ export function Root(props: IProps): React.ReactElement {
.loader(); .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" }]); l.language.tokenizer.root.unshift([new RegExp("\\bns\\b"), { token: "ns" }]);
for (const symbol of symbols) l.language.tokenizer.root.unshift([new RegExp(`\\b${symbol}\\b`), { token: "netscriptfunction" }]); for (const symbol of symbols)
l.language.tokenizer.root.unshift([new RegExp(`\\b${symbol}\\b`), { token: "netscriptfunction" }]);
const otherKeywords = ["let", "const", "var", "function"]; const otherKeywords = ["let", "const", "var", "function"];
const otherKeyvars = ["true", "false", "null", "undefined"]; const otherKeyvars = ["true", "false", "null", "undefined"];
otherKeywords.forEach((k) => l.language.tokenizer.root.unshift([new RegExp(`\\b${k}\\b`), { token: "otherkeywords" }])); otherKeywords.forEach((k) =>
otherKeyvars.forEach((k) => l.language.tokenizer.root.unshift([new RegExp(`\\b${k}\\b`), { token: "otherkeyvars" }])); l.language.tokenizer.root.unshift([new RegExp(`\\b${k}\\b`), { token: "otherkeywords" }]),
l.language.tokenizer.root.unshift([new RegExp('\\bthis\\b'), { token: "this" }]); );
otherKeyvars.forEach((k) =>
l.language.tokenizer.root.unshift([new RegExp(`\\b${k}\\b`), { token: "otherkeyvars" }]),
);
l.language.tokenizer.root.unshift([new RegExp("\\bthis\\b"), { token: "this" }]);
})(); })();
const source = (libSource + "").replace(/export /g, ""); const source = (libSource + "").replace(/export /g, "");
@ -446,7 +450,7 @@ export function Root(props: IProps): React.ReactElement {
} }
try { try {
infLoop(newCode); infLoop(newCode);
} catch (err) { } } catch (err) {}
} }
function saveScript(scriptToSave: OpenScript): void { function saveScript(scriptToSave: OpenScript): void {
@ -755,13 +759,16 @@ export function Root(props: IProps): React.ReactElement {
<Button <Button
onClick={() => onTabClick(index)} onClick={() => onTabClick(index)}
style={ style={
currentScript?.fileName === openScripts[index].fileName ? { currentScript?.fileName === openScripts[index].fileName
background: Settings.theme.button, ? {
color: Settings.theme.primary background: Settings.theme.button,
} : { color: Settings.theme.primary,
background: Settings.theme.backgroundsecondary, }
color: Settings.theme.secondary : {
}} background: Settings.theme.backgroundsecondary,
color: Settings.theme.secondary,
}
}
> >
{hostname}:~/{fileName} {dirty(index)} {hostname}:~/{fileName} {dirty(index)}
</Button> </Button>
@ -770,13 +777,15 @@ export function Root(props: IProps): React.ReactElement {
style={{ style={{
maxWidth: "20px", maxWidth: "20px",
minWidth: "20px", minWidth: "20px",
...(currentScript?.fileName === openScripts[index].fileName ? { ...(currentScript?.fileName === openScripts[index].fileName
background: Settings.theme.button, ? {
color: Settings.theme.primary background: Settings.theme.button,
} : { color: Settings.theme.primary,
background: Settings.theme.backgroundsecondary, }
color: Settings.theme.secondary : {
}) background: Settings.theme.backgroundsecondary,
color: Settings.theme.secondary,
}),
}} }}
> >
x x
@ -813,20 +822,30 @@ export function Root(props: IProps): React.ReactElement {
></Box> ></Box>
<Box display="flex" flexDirection="row" sx={{ m: 1 }} alignItems="center"> <Box display="flex" flexDirection="row" sx={{ m: 1 }} alignItems="center">
<Button startIcon={<SettingsIcon />} onClick={() => setOptionsOpen(true)} sx={{ mr: 1 }}>Options</Button> <Button startIcon={<SettingsIcon />} onClick={() => setOptionsOpen(true)} sx={{ mr: 1 }}>
Options
</Button>
<Button onClick={beautify}>Beautify</Button> <Button onClick={beautify}>Beautify</Button>
<Button color={updatingRam ? "secondary" : "primary"} sx={{ mx: 1 }} onClick={() => { setRamInfoOpen(true) }}> <Button
color={updatingRam ? "secondary" : "primary"}
sx={{ mx: 1 }}
onClick={() => {
setRamInfoOpen(true);
}}
>
{ram} {ram}
</Button> </Button>
<Button onClick={save}>Save (Ctrl/Cmd + s)</Button> <Button sx={{ mr: 1 }} onClick={save}>
Save (Ctrl/Cmd + s)
</Button>
<Button onClick={props.router.toTerminal}>Close (Ctrl/Cmd + b)</Button> <Button onClick={props.router.toTerminal}>Close (Ctrl/Cmd + b)</Button>
<Typography sx={{ mx: 1 }}> <Typography sx={{ mx: 1 }}>
{" "} {" "}
Documentation:{" "} <strong>Documentation:</strong>{" "}
<Link target="_blank" href="https://bitburner.readthedocs.io/en/latest/index.html"> <Link target="_blank" href="https://bitburner.readthedocs.io/en/latest/index.html">
Basic Basic
</Link>{" "} </Link>
| {" | "}
<Link target="_blank" href="https://github.com/danielyxie/bitburner/blob/dev/markdown/bitburner.ns.md"> <Link target="_blank" href="https://github.com/danielyxie/bitburner/blob/dev/markdown/bitburner.ns.md">
Full Full
</Link> </Link>
@ -858,7 +877,9 @@ export function Root(props: IProps): React.ReactElement {
<React.Fragment key={n + r}> <React.Fragment key={n + r}>
<TableRow> <TableRow>
<TableCell sx={{ color: Settings.theme.primary }}>{n}</TableCell> <TableCell sx={{ color: Settings.theme.primary }}>{n}</TableCell>
<TableCell align="right" sx={{ color: Settings.theme.primary }}>{r}</TableCell> <TableCell align="right" sx={{ color: Settings.theme.primary }}>
{r}
</TableCell>
</TableRow> </TableRow>
</React.Fragment> </React.Fragment>
))} ))}

@ -63,6 +63,11 @@ interface IDefaultSettings {
*/ */
Locale: string; Locale: string;
/**
* Limit the number of recently killed script entries being tracked.
*/
MaxRecentScriptsCapacity: number;
/** /**
* Limit the number of log entries for each script being executed on each server. * Limit the number of log entries for each script being executed on each server.
*/ */
@ -186,6 +191,7 @@ export const defaultSettings: IDefaultSettings = {
EnableBashHotkeys: false, EnableBashHotkeys: false,
TimestampsFormat: "", TimestampsFormat: "",
Locale: "en", Locale: "en",
MaxRecentScriptsCapacity: 50,
MaxLogCapacity: 50, MaxLogCapacity: 50,
MaxPortCapacity: 50, MaxPortCapacity: 50,
MaxTerminalCapacity: 500, MaxTerminalCapacity: 500,
@ -222,6 +228,7 @@ export const Settings: ISettings & ISelfInitializer & ISelfLoading = {
EnableBashHotkeys: defaultSettings.EnableBashHotkeys, EnableBashHotkeys: defaultSettings.EnableBashHotkeys,
TimestampsFormat: defaultSettings.TimestampsFormat, TimestampsFormat: defaultSettings.TimestampsFormat,
Locale: "en", Locale: "en",
MaxRecentScriptsCapacity: defaultSettings.MaxRecentScriptsCapacity,
MaxLogCapacity: defaultSettings.MaxLogCapacity, MaxLogCapacity: defaultSettings.MaxLogCapacity,
MaxPortCapacity: defaultSettings.MaxPortCapacity, MaxPortCapacity: defaultSettings.MaxPortCapacity,
MaxTerminalCapacity: defaultSettings.MaxTerminalCapacity, MaxTerminalCapacity: defaultSettings.MaxTerminalCapacity,

@ -26,7 +26,7 @@ import { convertTimeMsToTimeElapsedString } from "../../utils/StringHelperFuncti
import { arrayToString } from "../../utils/helpers/arrayToString"; import { arrayToString } from "../../utils/helpers/arrayToString";
import { Money } from "../React/Money"; import { Money } from "../React/Money";
import { MoneyRate } from "../React/MoneyRate"; import { MoneyRate } from "../React/MoneyRate";
import { RecentScript } from "../..//Netscript/RecentScripts"; import { RecentScript } from "../../Netscript/RecentScripts";
import { LogBoxEvents } from "../React/LogBoxManager"; import { LogBoxEvents } from "../React/LogBoxManager";
const useStyles = makeStyles({ const useStyles = makeStyles({

@ -67,6 +67,7 @@ export function GameOptionsRoot(props: IProps): React.ReactElement {
const importInput = useRef<HTMLInputElement>(null); const importInput = useRef<HTMLInputElement>(null);
const [execTime, setExecTime] = useState(Settings.CodeInstructionRunTime); const [execTime, setExecTime] = useState(Settings.CodeInstructionRunTime);
const [recentScriptsSize, setRecentScriptsSize] = useState(Settings.MaxRecentScriptsCapacity);
const [logSize, setLogSize] = useState(Settings.MaxLogCapacity); const [logSize, setLogSize] = useState(Settings.MaxLogCapacity);
const [portSize, setPortSize] = useState(Settings.MaxPortCapacity); const [portSize, setPortSize] = useState(Settings.MaxPortCapacity);
const [terminalSize, setTerminalSize] = useState(Settings.MaxTerminalCapacity); const [terminalSize, setTerminalSize] = useState(Settings.MaxTerminalCapacity);
@ -84,6 +85,11 @@ export function GameOptionsRoot(props: IProps): React.ReactElement {
Settings.CodeInstructionRunTime = newValue as number; Settings.CodeInstructionRunTime = newValue as number;
} }
function handleRecentScriptsSizeChange(event: any, newValue: number | number[]): void {
setRecentScriptsSize(newValue as number);
Settings.MaxRecentScriptsCapacity = newValue as number;
}
function handleLogSizeChange(event: any, newValue: number | number[]): void { function handleLogSizeChange(event: any, newValue: number | number[]): void {
setLogSize(newValue as number); setLogSize(newValue as number);
Settings.MaxLogCapacity = newValue as number; Settings.MaxLogCapacity = newValue as number;
@ -224,6 +230,26 @@ export function GameOptionsRoot(props: IProps): React.ReactElement {
valueLabelDisplay="auto" valueLabelDisplay="auto"
/> />
</ListItem> </ListItem>
<ListItem>
<Tooltip
title={
<Typography>
The maximum number of recently killed script entries being tracked. Setting this too high can cause
the game to use a lot of memory.
</Typography>
}
>
<Typography>Recently killed scripts size</Typography>
</Tooltip>
<Slider
value={recentScriptsSize}
onChange={handleRecentScriptsSizeChange}
step={25}
min={25}
max={500}
valueLabelDisplay="auto"
/>
</ListItem>
<ListItem> <ListItem>
<Tooltip <Tooltip
title={ title={