CODEBASE: Add Jsonable Map and Set types, move player.sourceFiles to a map (#473)

This commit is contained in:
Snarling 2023-04-18 03:19:45 -04:00 committed by GitHub
parent c44bdc1018
commit 0df984eea0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
55 changed files with 439 additions and 532 deletions

@ -66,11 +66,11 @@ function bitNodeFinishedState(): boolean {
}
function hasAccessToSF(player: PlayerObject, bn: number): boolean {
return player.bitNodeN === bn || player.sourceFiles.some((a) => a.n === bn);
return player.bitNodeN === bn || player.sourceFileLvl(bn) > 0;
}
function knowsAboutBitverse(player: PlayerObject): boolean {
return player.sourceFiles.some((a) => a.n === 1);
return player.sourceFiles.size > 0;
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
@ -274,7 +274,7 @@ export const achievements: Record<string, Achievement> = {
NS2: {
...achievementData["NS2"],
Icon: "ns2",
Condition: () => Player.getHomeComputer().scripts.some((s) => s.filename.endsWith(".js")),
Condition: () => [...Player.getHomeComputer().scripts.values()].some((s) => s.filename.endsWith(".js")),
},
FROZE: {
...achievementData["FROZE"],
@ -317,7 +317,7 @@ export const achievements: Record<string, Achievement> = {
SCRIPTS_30: {
...achievementData["SCRIPTS_30"],
Icon: "folders",
Condition: () => Player.getHomeComputer().scripts.length >= 30,
Condition: () => Player.getHomeComputer().scripts.size >= 30,
},
KARMA_1000000: {
...achievementData["KARMA_1000000"],
@ -342,7 +342,7 @@ export const achievements: Record<string, Achievement> = {
SCRIPT_32GB: {
...achievementData["SCRIPT_32GB"],
Icon: "bigcost",
Condition: () => Player.getHomeComputer().scripts.some((s) => (s.ramUsage ?? 0) >= 32),
Condition: () => [...Player.getHomeComputer().scripts.values()].some((s) => (s.ramUsage ?? 0) >= 32),
},
FIRST_HACKNET_NODE: {
...achievementData["FIRST_HACKNET_NODE"],

@ -1,37 +0,0 @@
/**
* React Component for displaying a list of the player's Source-Files
* on the Augmentations UI
*/
import * as React from "react";
import { Player } from "@player";
import { Settings } from "../../Settings/Settings";
import { OwnedAugmentationsOrderSetting } from "../../Settings/SettingEnums";
import { SourceFiles } from "../../SourceFile/SourceFiles";
import { SourceFileAccordion } from "../../ui/React/SourceFileAccordion";
export function OwnedSourceFiles(): React.ReactElement {
const sourceSfs = Player.sourceFiles.slice();
if (Settings.OwnedAugmentationsOrder === OwnedAugmentationsOrderSetting.Alphabetically) {
sourceSfs.sort((sf1, sf2) => {
return sf1.n - sf2.n;
});
}
return (
<>
{sourceSfs.map((e) => {
const srcFileKey = "SourceFile" + e.n;
const sfObj = SourceFiles[srcFileKey];
if (sfObj == null) {
console.error(`Invalid source file number: ${e.n}`);
return null;
}
return <SourceFileAccordion key={e.n} level={e.lvl} sf={sfObj} />;
})}
</>
);
}

@ -71,27 +71,24 @@ const getMaxLevel = (sfObj: SourceFile | SfMinus1): string | number => {
};
export function SourceFilesElement(): React.ReactElement {
const sourceSfs = Player.sourceFiles.slice();
const sourceFilesCopy = new Map(Player.sourceFiles);
const exploits = Player.exploits;
// Create a fake SF for -1, if "owned"
if (exploits.length > 0) {
sourceSfs.unshift({
n: -1,
lvl: exploits.length,
});
sourceFilesCopy.set(-1, exploits.length);
}
const sfList = [...sourceFilesCopy];
if (Settings.OwnedAugmentationsOrder === OwnedAugmentationsOrderSetting.Alphabetically) {
sourceSfs.sort((sf1, sf2) => {
return sf1.n - sf2.n;
});
sfList.sort(([n1, __lvl1], [n2, __lvl2]) => n1 - n2);
}
if (sourceSfs.length === 0) {
if (sfList.length === 0) {
return <></>;
}
const [selectedSf, setSelectedSf] = useState(sourceSfs[0]);
const firstEle = sfList[0];
const [selectedSf, setSelectedSf] = useState({ n: firstEle[0], lvl: firstEle[1] });
return (
<Box sx={{ width: "100%", mt: 1 }}>
@ -104,8 +101,8 @@ export function SourceFilesElement(): React.ReactElement {
sx={{ height: 400, overflowY: "scroll", borderRight: `1px solid ${Settings.theme.welllight}` }}
disablePadding
>
{sourceSfs.map((e, i) => {
const sfObj = safeGetSf(e.n);
{sfList.map(([n, lvl], i) => {
const sfObj = safeGetSf(n);
if (!sfObj) return;
const maxLevel = getMaxLevel(sfObj);
@ -113,8 +110,8 @@ export function SourceFilesElement(): React.ReactElement {
return (
<ListItemButton
key={i + 1}
onClick={() => setSelectedSf(e)}
selected={selectedSf.n === e.n}
onClick={() => setSelectedSf({ n, lvl })}
selected={selectedSf.n === n}
sx={{ py: 0 }}
>
<ListItemText
@ -122,7 +119,7 @@ export function SourceFilesElement(): React.ReactElement {
primary={<Typography>{sfObj.name}</Typography>}
secondary={
<Typography>
Level {e.lvl} / {maxLevel}
Level {lvl} / {maxLevel}
</Typography>
}
/>

@ -7,7 +7,6 @@ import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
import Typography from "@mui/material/Typography";
import Button from "@mui/material/Button";
import { PlayerOwnedSourceFile } from "../../SourceFile/PlayerOwnedSourceFile";
import { Player } from "@player";
import ButtonGroup from "@mui/material/ButtonGroup";
@ -21,20 +20,10 @@ export function SourceFiles(): React.ReactElement {
Player.hacknetNodes = [];
}
if (sfLvl === 0) {
Player.sourceFiles = Player.sourceFiles.filter((sf) => sf.n !== sfN);
Player.sourceFiles.delete(sfN);
return;
}
if (!Player.sourceFiles.some((sf) => sf.n === sfN)) {
Player.sourceFiles.push(new PlayerOwnedSourceFile(sfN, sfLvl));
return;
}
for (let i = 0; i < Player.sourceFiles.length; i++) {
if (Player.sourceFiles[i].n === sfN) {
Player.sourceFiles[i].lvl = sfLvl;
}
}
Player.sourceFiles.set(sfN, sfLvl);
};
}

@ -15,16 +15,17 @@ import Accordion from "@mui/material/Accordion";
import AccordionSummary from "@mui/material/AccordionSummary";
import AccordionDetails from "@mui/material/AccordionDetails";
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
import { ServerName } from "../Types/strings";
interface IServerProps {
hostname: string;
hostname: ServerName;
}
function ServerAccordion(props: IServerProps): React.ReactElement {
const server = GetServer(props.hostname);
if (server === null) throw new Error(`server '${props.hostname}' should not be null`);
let totalSize = 0;
for (const f of server.scripts) {
for (const f of server.scripts.values()) {
totalSize += f.code.length;
}
@ -43,7 +44,7 @@ function ServerAccordion(props: IServerProps): React.ReactElement {
const files: File[] = [];
for (const f of server.scripts) {
for (const f of server.scripts.values()) {
files.push({ name: f.filename, size: f.code.length });
}

@ -75,7 +75,7 @@ function initWebserver(): void {
return {
res: true,
data: {
files: home.scripts.map((script) => ({
files: [...home.scripts.values()].map((script) => ({
filename: script.filename,
code: script.code,
ramUsage: script.ramUsage,

@ -93,7 +93,7 @@ export function SpecialLocation(props: IProps): React.ReactElement {
function EatNoodles(): void {
SnackbarEvents.emit("You ate some delicious noodles and feel refreshed", ToastVariant.SUCCESS, 2000);
N00dles(); // This is the true power of the noodles.
if (Player.sourceFiles.length > 0) Player.giveExploit(Exploit.N00dles);
if (Player.sourceFiles.size > 0) Player.giveExploit(Exploit.N00dles);
if (Player.sourceFileLvl(5) > 0 || Player.bitNodeN === 5) {
Player.exp.intelligence *= 1.0000000000000002;
}

@ -80,7 +80,7 @@ function checkForMessagesToSend(): void {
}
//If the daemon can be hacked, send the player icarus.msg
if (Player.skills.hacking >= worldDaemon.requiredHackingSkill) {
sendMessage(redpill, Player.sourceFiles.length === 0);
sendMessage(redpill, Player.sourceFiles.size === 0);
}
//If the daemon cannot be hacked, send the player truthgazer.msg a single time.
else if (!recvd(truthGazer)) {

@ -96,16 +96,11 @@ export class WorkerScript {
if (server == null) {
throw new Error(`WorkerScript constructed with invalid server ip: ${this.hostname}`);
}
let found = false;
for (let i = 0; i < server.scripts.length; ++i) {
if (server.scripts[i].filename === this.name) {
found = true;
this.code = server.scripts[i].code;
}
}
if (!found) {
const script = server.scripts.get(this.name);
if (!script) {
throw new Error(`WorkerScript constructed with invalid script filename: ${this.name}`);
}
this.code = script.code;
this.scriptRef = runningScriptObj;
this.args = runningScriptObj.args.slice();
this.env = new Environment();
@ -127,16 +122,14 @@ export class WorkerScript {
*/
getScript(): Script | null {
const server = this.getServer();
for (let i = 0; i < server.scripts.length; ++i) {
if (server.scripts[i].filename === this.name) {
return server.scripts[i];
}
const script = server.scripts.get(this.name);
if (!script) {
console.error(
"Failed to find underlying Script object in WorkerScript.getScript(). This probably means somethings wrong",
);
return null;
}
console.error(
"Failed to find underlying Script object in WorkerScript.getScript(). This probably means somethings wrong",
);
return null;
return script;
}
/**
@ -144,17 +137,7 @@ export class WorkerScript {
* or null if it cannot be found
*/
getScriptOnServer(fn: string, server: BaseServer): Script | null {
if (server == null) {
server = this.getServer();
}
for (let i = 0; i < server.scripts.length; ++i) {
if (server.scripts[i].filename === fn) {
return server.scripts[i];
}
}
return null;
return server.scripts.get(fn) ?? null;
}
shouldLog(fn: string): boolean {

@ -36,7 +36,7 @@ import {
} from "./Server/ServerPurchases";
import { Server } from "./Server/Server";
import { influenceStockThroughServerGrow } from "./StockMarket/PlayerInfluencing";
import { areFilesEqual, isValidFilePath, removeLeadingSlash } from "./Terminal/DirectoryHelpers";
import { isValidFilePath, removeLeadingSlash } from "./Terminal/DirectoryHelpers";
import { TextFile, getTextFile, createTextFile } from "./TextFile";
import { runScriptFromScript } from "./NetscriptWorker";
import { killWorkerScript } from "./Netscript/killWorkerScript";
@ -877,7 +877,7 @@ export const ns: InternalAPI<NSFull> = {
}
// Scp for script files
const sourceScript = sourceServ.scripts.find((script) => script.filename === file);
const sourceScript = sourceServ.scripts.get(file);
if (!sourceScript) {
helpers.log(ctx, () => `File '${file}' does not exist.`);
noFailures = false;
@ -885,7 +885,7 @@ export const ns: InternalAPI<NSFull> = {
}
// Overwrite script if it already exists
const destScript = destServer.scripts.find((script) => script.filename === file);
const destScript = destServer.scripts.get(file);
if (destScript) {
if (destScript.code === sourceScript.code) {
helpers.log(ctx, () => `Identical file '${file}' was already on '${destServer?.hostname}'`);
@ -900,7 +900,7 @@ export const ns: InternalAPI<NSFull> = {
// Create new script if it does not already exist
const newScript = new Script(file, sourceScript.code, destServer.hostname);
destServer.scripts.push(newScript);
destServer.scripts.set(file, newScript);
helpers.log(ctx, () => `File '${file}' copied over to '${destServer?.hostname}'.`);
}
@ -915,7 +915,7 @@ export const ns: InternalAPI<NSFull> = {
...server.contracts.map((contract) => contract.fn),
...server.messages,
...server.programs,
...server.scripts.map((script) => script.filename),
...server.scripts.keys(),
...server.textFiles.map((textFile) => textFile.filename),
];
@ -1147,11 +1147,7 @@ export const ns: InternalAPI<NSFull> = {
const filename = helpers.string(ctx, "filename", _filename);
const hostname = helpers.string(ctx, "hostname", _hostname);
const server = helpers.getServer(ctx, hostname);
for (let i = 0; i < server.scripts.length; ++i) {
if (filename == server.scripts[i].filename) {
return true;
}
}
if (server.scripts.has(filename)) return true;
for (let i = 0; i < server.programs.length; ++i) {
if (filename.toLowerCase() == server.programs[i].toLowerCase()) {
return true;
@ -1366,47 +1362,45 @@ export const ns: InternalAPI<NSFull> = {
}
return writePort(portNumber, data);
},
write:
(ctx) =>
(_filename, _data = "", _mode = "a") => {
let fn = helpers.string(ctx, "handle", _filename);
const data = helpers.string(ctx, "data", _data);
const mode = helpers.string(ctx, "mode", _mode);
if (!isValidFilePath(fn)) throw helpers.makeRuntimeErrorMsg(ctx, `Invalid filepath: ${fn}`);
write: (ctx) => (_filename, _data, _mode) => {
let filename = helpers.string(ctx, "handle", _filename);
const data = helpers.string(ctx, "data", _data ?? "");
const mode = helpers.string(ctx, "mode", _mode ?? "a");
if (!isValidFilePath(filename)) throw helpers.makeRuntimeErrorMsg(ctx, `Invalid filepath: ${filename}`);
if (fn.lastIndexOf("/") === 0) fn = removeLeadingSlash(fn);
if (filename.lastIndexOf("/") === 0) filename = removeLeadingSlash(filename);
const server = helpers.getServer(ctx, ctx.workerScript.hostname);
const server = helpers.getServer(ctx, ctx.workerScript.hostname);
if (isScriptFilename(fn)) {
// Write to script
let script = ctx.workerScript.getScriptOnServer(fn, server);
if (script == null) {
// Create a new script
script = new Script(fn, String(data), server.hostname);
server.scripts.push(script);
return;
}
mode === "w" ? (script.code = String(data)) : (script.code += data);
// Set ram to null so a recalc is performed the next time ram usage is needed
script.invalidateModule();
if (isScriptFilename(filename)) {
// Write to script
let script = ctx.workerScript.getScriptOnServer(filename, server);
if (!script) {
// Create a new script
script = new Script(filename, String(data), server.hostname);
server.scripts.set(filename, script);
return;
} else {
// Write to text file
if (!fn.endsWith(".txt")) throw helpers.makeRuntimeErrorMsg(ctx, `Invalid filename: ${fn}`);
const txtFile = getTextFile(fn, server);
if (txtFile == null) {
createTextFile(fn, String(data), server);
return;
}
if (mode === "w") {
txtFile.write(String(data));
} else {
txtFile.append(String(data));
}
}
mode === "w" ? (script.code = data) : (script.code += data);
// Set ram to null so a recalc is performed the next time ram usage is needed
script.invalidateModule();
return;
},
} else {
// Write to text file
if (!filename.endsWith(".txt")) throw helpers.makeRuntimeErrorMsg(ctx, `Invalid filename: ${filename}`);
const txtFile = getTextFile(filename, server);
if (txtFile == null) {
createTextFile(filename, String(data), server);
return;
}
if (mode === "w") {
txtFile.write(String(data));
} else {
txtFile.append(String(data));
}
}
return;
},
tryWritePort: (ctx) => (_portNumber, data) => {
const portNumber = helpers.portNumber(ctx, _portNumber);
if (typeof data !== "string" && typeof data !== "number") {
@ -1515,21 +1509,19 @@ export const ns: InternalAPI<NSFull> = {
getScriptName: (ctx) => () => {
return ctx.workerScript.name;
},
getScriptRam:
(ctx) =>
(_scriptname, _hostname = ctx.workerScript.hostname) => {
const scriptname = helpers.string(ctx, "scriptname", _scriptname);
const hostname = helpers.string(ctx, "hostname", _hostname);
const server = helpers.getServer(ctx, hostname);
const script = server.scripts.find((serverScript) => areFilesEqual(serverScript.filename, scriptname));
if (!script) return 0;
const ramUsage = script.getRamUsage(server.scripts);
if (!ramUsage) {
helpers.log(ctx, () => `Could not calculate ram usage for ${scriptname} on ${hostname}.`);
return 0;
}
return ramUsage;
},
getScriptRam: (ctx) => (_scriptname, _hostname) => {
const scriptname = helpers.string(ctx, "scriptname", _scriptname);
const hostname = helpers.string(ctx, "hostname", _hostname ?? ctx.workerScript.hostname);
const server = helpers.getServer(ctx, hostname);
const script = server.scripts.get(scriptname);
if (!script) return 0;
const ramUsage = script.getRamUsage(server.scripts);
if (!ramUsage) {
helpers.log(ctx, () => `Could not calculate ram usage for ${scriptname} on ${hostname}.`);
return 0;
}
return ramUsage;
},
getRunningScript:
(ctx) =>
(fn, hostname, ...args) => {
@ -1781,7 +1773,7 @@ export const ns: InternalAPI<NSFull> = {
}; // Wrap the user function to prevent WorkerScript leaking as 'this'
},
mv: (ctx) => (_host, _source, _destination) => {
const host = helpers.string(ctx, "host", _host);
const hostname = helpers.string(ctx, "host", _host);
const source = helpers.string(ctx, "source", _source);
const destination = helpers.string(ctx, "destination", _destination);
@ -1800,44 +1792,40 @@ export const ns: InternalAPI<NSFull> = {
return;
}
const destServer = helpers.getServer(ctx, host);
const server = helpers.getServer(ctx, hostname);
if (!source_is_txt && destServer.isRunning(source))
if (!source_is_txt && server.isRunning(source))
throw helpers.makeRuntimeErrorMsg(ctx, `Cannot use 'mv' on a script that is running`);
interface File {
filename: string;
}
let source_file: File | undefined;
let dest_file: File | undefined;
const files = source_is_txt ? destServer.textFiles : destServer.scripts;
let source_file: File | null = null;
let dest_file: File | null = null;
for (let i = 0; i < files.length; ++i) {
const file = files[i];
if (file.filename === source) {
source_file = file;
} else if (file.filename === destination) {
dest_file = file;
}
if (source_is_txt) {
// Traverses twice potentially. Inefficient but will soon be replaced with a map.
source_file = server.textFiles.find((textFile) => textFile.filename === source);
dest_file = server.textFiles.find((textFile) => textFile.filename === destination);
} else {
source_file = server.scripts.get(source);
dest_file = server.scripts.get(destination);
}
if (!source_file) throw helpers.makeRuntimeErrorMsg(ctx, `Source file ${source} does not exist`);
if (source_file == null) throw helpers.makeRuntimeErrorMsg(ctx, `Source file ${source} does not exist`);
if (dest_file != null) {
if (dest_file) {
if (dest_file instanceof TextFile && source_file instanceof TextFile) {
dest_file.text = source_file.text;
} else if (dest_file instanceof Script && source_file instanceof Script) {
dest_file.code = source_file.code;
// Source needs to be invalidated as well, to invalidate its dependents
source_file.invalidateModule();
dest_file.invalidateModule();
}
destServer.removeFile(source);
server.removeFile(source);
} else {
source_file.filename = destination;
if (source_file instanceof Script) {
source_file.invalidateModule();
}
if (source_file instanceof Script) source_file.invalidateModule();
}
},
flags: Flags,

@ -15,7 +15,7 @@ export function NetscriptBladeburner(): InternalAPI<INetscriptBladeburner> {
return;
};
const getBladeburner = function (ctx: NetscriptContext): Bladeburner {
const apiAccess = Player.bitNodeN === 7 || Player.sourceFiles.some((a) => a.n === 7);
const apiAccess = Player.bitNodeN === 7 || Player.sourceFileLvl(7) > 0;
if (!apiAccess) {
throw helpers.makeRuntimeErrorMsg(ctx, "You have not unlocked the bladeburner API.", "API ACCESS");
}

@ -94,7 +94,7 @@ export function NetscriptFormulas(): InternalAPI<IFormulas> {
numPeopleKilled: 0,
money: 0,
city: CityName.Sector12,
location: "",
location: LocationName.TravelAgency,
bitNodeN: 0,
totalPlaytime: 0,
jobs: {},

@ -50,7 +50,6 @@ import { canGetBonus, onExport } from "../ExportBonus";
import { saveObject } from "../SaveObject";
import { calculateCrimeWorkStats } from "../Work/Formulas";
import { findEnumMember } from "../utils/helpers/enum";
import { areFilesEqual } from "../Terminal/DirectoryHelpers";
import { Engine } from "../engine";
import { checkEnum } from "../utils/helpers/enum";
@ -81,7 +80,7 @@ export function NetscriptSingularity(): InternalAPI<ISingularity> {
//Run a script after reset
if (!cbScript) return;
const home = Player.getHomeComputer();
const script = home.scripts.find((serverScript) => areFilesEqual(serverScript.filename, cbScript));
const script = home.scripts.get(cbScript);
if (!script) return;
const ramUsage = script.getRamUsage(home.scripts);
if (!ramUsage) {
@ -110,9 +109,7 @@ export function NetscriptSingularity(): InternalAPI<ISingularity> {
return res;
},
getOwnedSourceFiles: () => () => {
return Player.sourceFiles.map((sf) => {
return { n: sf.n, lvl: sf.lvl };
});
return [...Player.sourceFiles].map(([n, lvl]) => ({ n, lvl }));
},
getAugmentationsFromFaction: (ctx) => (_facName) => {
helpers.checkSingularityAccess(ctx);

@ -7,7 +7,8 @@ import { parse } from "acorn";
import { LoadedModule, ScriptURL, ScriptModule } from "./Script/LoadedModule";
import { Script } from "./Script/Script";
import { areImportsEquals, removeLeadingSlash } from "./Terminal/DirectoryHelpers";
import { removeLeadingSlash } from "./Terminal/DirectoryHelpers";
import { ScriptFilename, scriptFilenameFromImport } from "./Types/strings";
// Acorn type def is straight up incomplete so we have to fill with our own.
export type Node = any;
@ -46,7 +47,7 @@ const cleanup = new FinalizationRegistry((mapKey: string) => {
}
});
export function compile(script: Script, scripts: Script[]): Promise<ScriptModule> {
export function compile(script: Script, scripts: Map<ScriptFilename, Script>): Promise<ScriptModule> {
// Return the module if it already exists
if (script.mod) return script.mod.module;
@ -75,7 +76,7 @@ function addDependencyInfo(script: Script, seenStack: Script[]) {
* @param scripts array of other scripts on the server
* @param seenStack A stack of scripts that were higher up in the import tree in a recursive call.
*/
function generateLoadedModule(script: Script, scripts: Script[], seenStack: Script[]): LoadedModule {
function generateLoadedModule(script: Script, scripts: Map<ScriptFilename, Script>, seenStack: Script[]): LoadedModule {
// Early return for recursive calls where the script already has a URL
if (script.mod) {
addDependencyInfo(script, seenStack);
@ -124,10 +125,10 @@ function generateLoadedModule(script: Script, scripts: Script[], seenStack: Scri
let newCode = script.code;
// Loop through each node and replace the script name with a blob url.
for (const node of importNodes) {
const filename = node.filename.startsWith("./") ? node.filename.substring(2) : node.filename;
const filename = scriptFilenameFromImport(node.filename);
// Find the corresponding script.
const importedScript = scripts.find((s) => areImportsEquals(s.filename, filename));
const importedScript = scripts.get(filename);
if (!importedScript) continue;
seenStack.push(script);

@ -29,10 +29,10 @@ import { roundToTwo } from "./utils/helpers/roundToTwo";
import { parse } from "acorn";
import { simple as walksimple } from "acorn-walk";
import { areFilesEqual } from "./Terminal/DirectoryHelpers";
import { Terminal } from "./Terminal";
import { ScriptArg } from "@nsdefs";
import { handleUnknownError, CompleteRunOptions } from "./Netscript/NetscriptHelpers";
import { scriptFilenameFromImport } from "./Types/strings";
export const NetscriptPorts: Map<PortNumber, Port> = new Map();
@ -147,12 +147,7 @@ function processNetscript1Imports(code: string, workerScript: WorkerScript): { c
}
function getScript(scriptName: string): Script | null {
for (let i = 0; i < server.scripts.length; ++i) {
if (server.scripts[i].filename === scriptName) {
return server.scripts[i];
}
}
return null;
return server.scripts.get(scriptName) ?? null;
}
let generatedCode = ""; // Generated Javascript Code
@ -162,10 +157,7 @@ function processNetscript1Imports(code: string, workerScript: WorkerScript): { c
walksimple(ast, {
ImportDeclaration: (node: Node) => {
hasImports = true;
let scriptName = node.source.value;
if (scriptName.startsWith("./")) {
scriptName = scriptName.slice(2);
}
const scriptName = scriptFilenameFromImport(node.source.value, true);
const script = getScript(scriptName);
if (script == null) {
throw new Error("'Import' failed due to invalid script: " + scriptName);
@ -398,7 +390,7 @@ export function runScriptFromScript(
* running a large number of scripts. */
// Find the script, fail if it doesn't exist.
const script = host.scripts.find((serverScript) => areFilesEqual(serverScript.filename, scriptname));
const script = host.scripts.get(scriptname);
if (!script) {
workerScript.log(caller, () => `Could not find script '${scriptname}' on '${host.hostname}'`);
return 0;

@ -8,7 +8,6 @@ import * as workMethods from "./PlayerObjectWorkMethods";
import { setPlayer } from "../../Player";
import { Sleeve } from "../Sleeve/Sleeve";
import { PlayerOwnedSourceFile } from "../../SourceFile/PlayerOwnedSourceFile";
import { Exploit } from "../../Exploits/Exploit";
import { LocationName } from "../../Enums";
@ -21,6 +20,7 @@ import { HashManager } from "../../Hacknet/HashManager";
import { MoneySourceTracker } from "../../utils/MoneySourceTracker";
import { constructorsForReviver, Generic_toJSON, Generic_fromJSON, IReviverValue } from "../../utils/JSONReviver";
import { JSONMap } from "../../Types/Jsonable";
import { PlayerAchievement } from "../../Achievements/Achievements";
import { cyrb53 } from "../../utils/StringHelperFunctions";
import { getRandomInt } from "../../utils/helpers/getRandomInt";
@ -59,7 +59,7 @@ export class PlayerObject extends Person implements IPlayer {
scriptProdSinceLastAug = 0;
sleeves: Sleeve[] = [];
sleevesFromCovenant = 0;
sourceFiles: PlayerOwnedSourceFile[] = [];
sourceFiles: JSONMap<number, number> = new JSONMap();
exploits: Exploit[] = [];
achievements: PlayerAchievement[] = [];
terminalCommandHistory: string[] = [];
@ -168,9 +168,15 @@ export class PlayerObject extends Person implements IPlayer {
/** Initializes a PlayerObject object from a JSON save state. */
static fromJSON(value: IReviverValue): PlayerObject {
if (!value.data.hp?.current || !value.data.hp?.max) value.data.hp = { current: 10, max: 10 };
const player = Generic_fromJSON(PlayerObject, value.data);
if (player.money === null) player.money = 0;
player.hp = { current: player.hp?.current ?? 10, max: player.hp?.max ?? 10 };
player.money ??= 0;
player.updateSkillLevels();
if (Array.isArray(player.sourceFiles)) {
// Expect pre-2.3 sourcefile format here.
type OldSourceFiles = { n: number; lvl: number }[];
player.sourceFiles = new JSONMap((player.sourceFiles as OldSourceFiles).map(({ n, lvl }) => [n, lvl]));
}
return player;
}
}

@ -586,14 +586,14 @@ export function reapplyAllSourceFiles(this: PlayerObject): void {
//Will always be called after reapplyAllAugmentations() so multipliers do not have to be reset
//this.resetMultipliers();
for (let i = 0; i < this.sourceFiles.length; ++i) {
const srcFileKey = "SourceFile" + this.sourceFiles[i].n;
for (const [bn, lvl] of this.sourceFiles) {
const srcFileKey = "SourceFile" + bn;
const sourceFileObject = SourceFiles[srcFileKey];
if (sourceFileObject == null) {
console.error(`Invalid source file number: ${this.sourceFiles[i].n}`);
if (!sourceFileObject) {
console.error(`Invalid source file number: ${bn}`);
continue;
}
applySourceFile(this.sourceFiles[i]);
applySourceFile(bn, lvl);
}
applyExploit();
this.updateSkillLevels();
@ -1222,9 +1222,7 @@ export function canAccessCotMG(this: PlayerObject): boolean {
}
export function sourceFileLvl(this: PlayerObject, n: number): number {
const sf = this.sourceFiles.find((sf) => sf.n === n);
if (!sf) return 0;
return sf.lvl;
return this.sourceFiles.get(n) ?? 0;
}
export function focusPenalty(this: PlayerObject): number {

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

@ -192,7 +192,7 @@ export function prestigeSourceFile(flume: boolean): void {
AddToAllServers(homeComp);
prestigeHomeComputer(homeComp);
// Ram usage needs to be cleared for bitnode-level resets, due to possible change in singularity cost.
for (const script of homeComp.scripts) script.ramUsage = null;
for (const script of homeComp.scripts.values()) script.ramUsage = null;
// Re-create foreign servers
initForeignServers(Player.getHomeComputer());

@ -20,7 +20,7 @@ function requireHackingLevel(lvl: number) {
function bitFlumeRequirements() {
return function () {
return Player.sourceFiles.length > 0 && Player.skills.hacking >= 1;
return Player.sourceFiles.size > 0 && Player.skills.hacking >= 1;
};
}

@ -1,7 +1,6 @@
/** Implementation for what happens when you destroy a BitNode */
import React from "react";
import { Player } from "@player";
import { PlayerOwnedSourceFile } from "./SourceFile/PlayerOwnedSourceFile";
import { SourceFiles } from "./SourceFile/SourceFiles";
import { dialogBoxCreate } from "./ui/React/DialogBox";
@ -12,32 +11,26 @@ import { Engine } from "./engine";
function giveSourceFile(bitNodeNumber: number): void {
const sourceFileKey = "SourceFile" + bitNodeNumber.toString();
const sourceFile = SourceFiles[sourceFileKey];
if (sourceFile == null) {
if (!sourceFile) {
console.error(`Could not find source file for Bit node: ${bitNodeNumber}`);
return;
}
// Check if player already has this source file
const ownedSourceFile = Player.sourceFiles.find((sourceFile) => sourceFile.n === bitNodeNumber);
let lvl = Player.sourceFileLvl(bitNodeNumber);
if (ownedSourceFile) {
if (ownedSourceFile.lvl >= 3 && ownedSourceFile.n !== 12) {
if (lvl > 0) {
if (lvl >= 3 && bitNodeNumber !== 12) {
dialogBoxCreate(
`The Source-File for the BitNode you just destroyed, ${sourceFile.name}, is already at max level!`,
);
} else {
++ownedSourceFile.lvl;
dialogBoxCreate(
sourceFile.name +
" was upgraded to level " +
ownedSourceFile.lvl +
" for " +
"destroying its corresponding BitNode!",
);
lvl++;
Player.sourceFiles.set(bitNodeNumber, lvl);
dialogBoxCreate(`${sourceFile.name} was upgraded to level ${lvl} for destroying its corresponding BitNode!`);
}
} else {
const newSrcFile = new PlayerOwnedSourceFile(bitNodeNumber, 1);
Player.sourceFiles.push(newSrcFile);
Player.sourceFiles.set(bitNodeNumber, 1);
if (bitNodeNumber === 5 && Player.skills.intelligence === 0) {
Player.skills.intelligence = 1;
}

@ -87,10 +87,7 @@ export const RFARequestHandler: Record<string, (message: RFAMessage) => void | R
const server = GetServer(msg.params.server);
if (!server) return error("Server hostname invalid", msg);
const fileNameList: string[] = [
...server.textFiles.map((txt): string => txt.filename),
...server.scripts.map((scr): string => scr.filename),
];
const fileNameList: string[] = [...server.textFiles.map((txt): string => txt.filename), ...server.scripts.keys()];
return new RFAMessage({ result: fileNameList, id: msg.id });
},
@ -105,10 +102,8 @@ export const RFARequestHandler: Record<string, (message: RFAMessage) => void | R
...server.textFiles.map((txt): FileContent => {
return { filename: txt.filename, content: txt.text };
}),
...server.scripts.map((scr): FileContent => {
return { filename: scr.filename, content: scr.code };
}),
];
for (const [filename, script] of server.scripts) fileList.push({ filename, content: script.code });
return new RFAMessage({ result: fileList, id: msg.id });
},

@ -3,7 +3,7 @@ import { Companies, loadCompanies } from "./Company/Companies";
import { CONSTANTS } from "./Constants";
import { Factions, loadFactions } from "./Faction/Factions";
import { loadAllGangs, AllGangs } from "./Gang/AllGangs";
import { Player, loadPlayer } from "./Player";
import { Player, setPlayer, loadPlayer } from "./Player";
import {
saveAllServers,
loadAllServers,
@ -27,7 +27,6 @@ import { AwardNFG, v1APIBreak } from "./utils/v1APIBreak";
import { AugmentationNames } from "./Augmentation/data/AugmentationNames";
import { PlayerOwnedAugmentation } from "./Augmentation/PlayerOwnedAugmentation";
import { LocationName } from "./Enums";
import { PlayerObject } from "./PersonObjects/Player/PlayerObject";
import { pushGameSaved } from "./Electron";
import { defaultMonacoTheme } from "./ScriptEditor/ui/themes";
import { FactionNames } from "./Faction/data/FactionNames";
@ -35,6 +34,8 @@ import { Faction } from "./Faction/Faction";
import { safelyCreateUniqueServer } from "./Server/ServerHelpers";
import { SpecialServers } from "./Server/data/SpecialServers";
import { v2APIBreak } from "./utils/v2APIBreak";
import { Script } from "./Script/Script";
import { JSONMap } from "./Types/Jsonable";
/* SaveObject.js
* Defines the object used to save/load games
@ -208,7 +209,7 @@ class BitburnerSaveObject {
base64: base64Save,
};
const importedPlayer = PlayerObject.fromJSON(JSON.parse(parsedSave.data.PlayerSave));
const importedPlayer = loadPlayer(parsedSave.data.PlayerSave);
const playerData: ImportPlayerData = {
identifier: importedPlayer.identifier,
@ -224,7 +225,7 @@ class BitburnerSaveObject {
bitNode: importedPlayer.bitNodeN,
bitNodeLevel: importedPlayer.sourceFileLvl(Player.bitNodeN) + 1,
sourceFiles: importedPlayer.sourceFiles?.reduce<number>((total, current) => (total += current.lvl), 0) ?? 0,
sourceFiles: [...importedPlayer.sourceFiles].reduce<number>((total, [__bn, lvl]) => (total += lvl), 0),
};
data.playerData = playerData;
@ -345,7 +346,7 @@ function evaluateVersionCompatibility(ver: string | number): void {
}
return code;
}
for (const server of GetAllServers()) {
for (const server of GetAllServers() as unknown as { scripts: Script[] }[]) {
for (const script of server.scripts) {
script.code = convert(script.code);
}
@ -663,6 +664,15 @@ function evaluateVersionCompatibility(ver: string | number): void {
}
anyPlayer.lastAugReset ??= anyPlayer.lastUpdate - anyPlayer.playtimeSinceLastAug;
anyPlayer.lastNodeReset ??= anyPlayer.lastUpdate - anyPlayer.playtimeSinceLastBitnode;
for (const server of GetAllServers()) {
if (Array.isArray(server.scripts)) {
const oldScripts = server.scripts as Script[];
server.scripts = new JSONMap();
for (const script of oldScripts) {
server.scripts.set(script.filename, script);
}
}
}
}
}
@ -673,7 +683,7 @@ function loadGame(saveString: string): boolean {
const saveObj = JSON.parse(saveString, Reviver);
loadPlayer(saveObj.PlayerSave);
setPlayer(loadPlayer(saveObj.PlayerSave));
loadAllServers(saveObj.AllServersSave);
loadCompanies(saveObj.CompaniesSave);
loadFactions(saveObj.FactionsSave);

@ -12,8 +12,8 @@ import { RamCalculationErrorCode } from "./RamCalculationErrorCodes";
import { RamCosts, RamCostConstants } from "../Netscript/RamCostGenerator";
import { Script } from "./Script";
import { areImportsEquals } from "../Terminal/DirectoryHelpers";
import { Node } from "../NetscriptJSEvaluator";
import { ScriptFilename, scriptFilenameFromImport } from "../Types/strings";
export interface RamUsageEntry {
type: "ns" | "dom" | "fn" | "misc";
@ -40,7 +40,7 @@ const memCheckGlobalKey = ".__GLOBAL__";
* RAM usage. Also accounts for imported modules.
* @param {Script[]} otherScripts - All other scripts on the server. Used to account for imported scripts
* @param {string} code - The code being parsed */
function parseOnlyRamCalculate(otherScripts: Script[], code: string): RamCalculation {
function parseOnlyRamCalculate(otherScripts: Map<ScriptFilename, Script>, code: string, ns1?: boolean): RamCalculation {
try {
/**
* Maps dependent identifiers to their dependencies.
@ -86,16 +86,9 @@ function parseOnlyRamCalculate(otherScripts: Script[], code: string): RamCalcula
if (nextModule === undefined) throw new Error("nextModule should not be undefined");
if (nextModule.startsWith("https://") || nextModule.startsWith("http://")) continue;
let script = null;
const fn = nextModule.startsWith("./") ? nextModule.slice(2) : nextModule;
for (const s of otherScripts) {
if (areImportsEquals(s.filename, fn)) {
script = s;
break;
}
}
if (script == null) {
const filename = scriptFilenameFromImport(nextModule, ns1);
const script = otherScripts.get(filename);
if (!script) {
return { cost: RamCalculationErrorCode.ImportError }; // No such script on the server
}
@ -375,9 +368,13 @@ function parseOnlyCalculateDeps(code: string, currentModule: string): ParseDepsR
* @param {Script[]} otherScripts - All other scripts on the server.
* Used to account for imported scripts
*/
export function calculateRamUsage(codeCopy: string, otherScripts: Script[]): RamCalculation {
export function calculateRamUsage(
codeCopy: string,
otherScripts: Map<ScriptFilename, Script>,
ns1?: boolean,
): RamCalculation {
try {
return parseOnlyRamCalculate(otherScripts, codeCopy);
return parseOnlyRamCalculate(otherScripts, codeCopy, ns1);
} catch (e) {
console.error(`Failed to parse script for RAM calculations:`);
console.error(e);

@ -10,6 +10,7 @@ import { LoadedModule, ScriptURL } from "./LoadedModule";
import { Generic_fromJSON, Generic_toJSON, IReviverValue, constructorsForReviver } from "../utils/JSONReviver";
import { roundToTwo } from "../utils/helpers/roundToTwo";
import { RamCostConstants } from "../Netscript/RamCostGenerator";
import { ScriptFilename } from "src/Types/strings";
export class Script {
code: string;
@ -81,7 +82,7 @@ export class Script {
}
/** Gets the ram usage, while also attempting to update it if it's currently null */
getRamUsage(otherScripts: Script[]): number | null {
getRamUsage(otherScripts: Map<ScriptFilename, Script>): number | null {
if (this.ramUsage) return this.ramUsage;
this.updateRamUsage(otherScripts);
return this.ramUsage;
@ -91,8 +92,8 @@ export class Script {
* Calculates and updates the script's RAM usage based on its code
* @param {Script[]} otherScripts - Other scripts on the server. Used to process imports
*/
updateRamUsage(otherScripts: Script[]): void {
const ramCalc = calculateRamUsage(this.code, otherScripts);
updateRamUsage(otherScripts: Map<ScriptFilename, Script>): void {
const ramCalc = calculateRamUsage(this.code, otherScripts, this.filename.endsWith(".script"));
if (ramCalc.cost >= RamCostConstants.Base) {
this.ramUsage = roundToTwo(ramCalc.cost);
this.ramUsageEntries = ramCalc.entries as RamUsageEntry[];

@ -432,19 +432,18 @@ export function Root(props: IProps): React.ReactElement {
if (server === null) throw new Error("Server should not be null but it is.");
if (isScriptFilename(scriptToSave.fileName)) {
//If the current script already exists on the server, overwrite it
for (let i = 0; i < server.scripts.length; i++) {
if (scriptToSave.fileName == server.scripts[i].filename) {
server.scripts[i].saveScript(scriptToSave.fileName, scriptToSave.code, Player.currentServer);
if (Settings.SaveGameOnFileSave) saveObject.saveGame();
Router.toPage(Page.Terminal);
return;
}
const existingScript = server.scripts.get(scriptToSave.fileName);
if (existingScript) {
existingScript.saveScript(scriptToSave.fileName, scriptToSave.code, Player.currentServer);
if (Settings.SaveGameOnFileSave) saveObject.saveGame();
Router.toPage(Page.Terminal);
return;
}
//If the current script does NOT exist, create a new one
const script = new Script();
script.saveScript(scriptToSave.fileName, scriptToSave.code, Player.currentServer);
server.scripts.push(script);
server.scripts.set(scriptToSave.fileName, script);
} else if (scriptToSave.isTxt) {
for (let i = 0; i < server.textFiles.length; ++i) {
if (server.textFiles[i].fn === scriptToSave.fileName) {
@ -509,19 +508,18 @@ export function Root(props: IProps): React.ReactElement {
if (server === null) throw new Error("Server should not be null but it is.");
if (isScriptFilename(currentScript.fileName)) {
//If the current script already exists on the server, overwrite it
for (let i = 0; i < server.scripts.length; i++) {
if (currentScript.fileName == server.scripts[i].filename) {
server.scripts[i].saveScript(currentScript.fileName, currentScript.code, Player.currentServer);
if (Settings.SaveGameOnFileSave) saveObject.saveGame();
rerender();
return;
}
const existingScript = server.scripts.get(currentScript.fileName);
if (existingScript) {
existingScript.saveScript(currentScript.fileName, currentScript.code, Player.currentServer);
if (Settings.SaveGameOnFileSave) saveObject.saveGame();
rerender();
return;
}
//If the current script does NOT exist, create a new one
const script = new Script();
script.saveScript(currentScript.fileName, currentScript.code, Player.currentServer);
server.scripts.push(script);
server.scripts.set(currentScript.fileName, script);
} else if (currentScript.isTxt) {
for (let i = 0; i < server.textFiles.length; ++i) {
if (server.textFiles[i].fn === currentScript.fileName) {
@ -683,7 +681,7 @@ export function Root(props: IProps): React.ReactElement {
if (server === null) throw new Error(`Server '${openScript.hostname}' should not be null, but it is.`);
const data = openScript.isTxt
? server.textFiles.find((t) => t.filename === openScript.fileName)?.text
: server.scripts.find((s) => s.filename === openScript.fileName)?.code;
: server.scripts.get(openScript.fileName)?.code;
return data ?? null;
}
function handleFilterChange(event: React.ChangeEvent<HTMLInputElement>): void {

@ -8,11 +8,11 @@ import { IMinMaxRange } from "../types";
import { createRandomIp } from "../utils/IPAddress";
import { getRandomInt } from "../utils/helpers/getRandomInt";
import { Reviver } from "../utils/JSONReviver";
import { isValidIPAddress } from "../utils/helpers/isValidIPAddress";
import { SpecialServers } from "./data/SpecialServers";
import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers";
import "../Script/RunningScript"; // For reviver side-effect
import { IPAddress, isIPAddress } from "../Types/strings";
import type { RunningScript } from "../Script/RunningScript";
/**
@ -50,9 +50,7 @@ export function GetServer(s: string): BaseServer | null {
if (server) return server;
}
if (!isValidIPAddress(s)) {
return GetServerByHostname(s);
}
if (!isIPAddress(s)) return GetServerByHostname(s);
const ipserver = GetServerByIP(s);
if (ipserver !== undefined) {
@ -88,8 +86,8 @@ export function ipExists(ip: string): boolean {
return false;
}
export function createUniqueRandomIp(): string {
let ip: string;
export function createUniqueRandomIp(): IPAddress {
let ip: IPAddress;
// Repeat generating ip, until unique one is found
do {
ip = createRandomIp();
@ -117,7 +115,7 @@ export const renameServer = (hostname: string, newName: string): void => {
interface IServerParams {
hackDifficulty?: number;
hostname: string;
ip: string;
ip: IPAddress;
maxRam?: number;
moneyAvailable?: number;
numOpenPortsRequired: number;

@ -11,11 +11,13 @@ import { isScriptFilename } from "../Script/isScriptFilename";
import { createRandomIp } from "../utils/IPAddress";
import { compareArrays } from "../utils/helpers/compareArrays";
import { ScriptArg } from "../Netscript/ScriptArg";
import { JSONMap } from "../Types/Jsonable";
import { IPAddress, ScriptFilename, ServerName } from "../Types/strings";
interface IConstructorParams {
adminRights?: boolean;
hostname: string;
ip?: string;
ip?: IPAddress;
isConnectedTo?: boolean;
maxRam?: number;
organizationName?: string;
@ -42,13 +44,13 @@ export abstract class BaseServer implements IServer {
hasAdminRights = false;
// Hostname. Must be unique
hostname = "";
hostname: ServerName = "home";
// Flag indicating whether HTTP Port is open
httpPortOpen = false;
// IP Address. Must be unique
ip = "";
ip = "1.1.1.1" as IPAddress;
// Flag indicating whether player is currently connected to this server
isConnectedTo = false;
@ -73,7 +75,7 @@ export abstract class BaseServer implements IServer {
runningScripts: RunningScript[] = [];
// Script files on this Server
scripts: Script[] = [];
scripts: JSONMap<ScriptFilename, Script> = new JSONMap();
// Contains the hostnames of all servers that are immediately
// reachable from this one
@ -157,13 +159,7 @@ export abstract class BaseServer implements IServer {
* Script object on the server (if it exists)
*/
getScript(scriptName: string): Script | null {
for (let i = 0; i < this.scripts.length; i++) {
if (this.scripts[i].filename === scriptName) {
return this.scripts[i];
}
}
return null;
return this.scripts.get(scriptName) ?? null;
}
/** Returns boolean indicating whether the given script is running on this server */
@ -184,44 +180,44 @@ export abstract class BaseServer implements IServer {
/**
* Remove a file from the server
* @param fn {string} Name of file to be deleted
* @param filename {string} Name of file to be deleted
* @returns {IReturnStatus} Return status object indicating whether or not file was deleted
*/
removeFile(fn: string): IReturnStatus {
if (fn.endsWith(".exe") || fn.match(/^.+\.exe-\d+(?:\.\d*)?%-INC$/) != null) {
removeFile(filename: string): IReturnStatus {
if (filename.endsWith(".exe") || filename.match(/^.+\.exe-\d+(?:\.\d*)?%-INC$/) != null) {
for (let i = 0; i < this.programs.length; ++i) {
if (this.programs[i] === fn) {
if (this.programs[i] === filename) {
this.programs.splice(i, 1);
return { res: true };
}
}
} else if (isScriptFilename(fn)) {
const scriptIndex = this.scripts.findIndex((script) => script.filename === fn);
if (scriptIndex === -1) return { res: false, msg: `script ${fn} not found.` };
if (this.isRunning(fn)) {
} else if (isScriptFilename(filename)) {
const script = this.scripts.get(filename);
if (!script) return { res: false, msg: `script ${filename} not found.` };
if (this.isRunning(filename)) {
return { res: false, msg: "Cannot delete a script that is currently running!" };
}
this.scripts[scriptIndex].invalidateModule();
this.scripts.splice(scriptIndex, 1);
script.invalidateModule();
this.scripts.delete(filename);
return { res: true };
} else if (fn.endsWith(".lit")) {
} else if (filename.endsWith(".lit")) {
for (let i = 0; i < this.messages.length; ++i) {
const f = this.messages[i];
if (typeof f === "string" && f === fn) {
if (typeof f === "string" && f === filename) {
this.messages.splice(i, 1);
return { res: true };
}
}
} else if (fn.endsWith(".txt")) {
} else if (filename.endsWith(".txt")) {
for (let i = 0; i < this.textFiles.length; ++i) {
if (this.textFiles[i].fn === fn) {
if (this.textFiles[i].fn === filename) {
this.textFiles.splice(i, 1);
return { res: true };
}
}
} else if (fn.endsWith(".cct")) {
} else if (filename.endsWith(".cct")) {
for (let i = 0; i < this.contracts.length; ++i) {
if (this.contracts[i].fn === fn) {
if (this.contracts[i].fn === filename) {
this.contracts.splice(i, 1);
return { res: true };
}
@ -266,30 +262,23 @@ export abstract class BaseServer implements IServer {
* Write to a script file
* Overwrites existing files. Creates new files if the script does not exist.
*/
writeToScriptFile(fn: string, code: string): writeResult {
const ret = { success: false, overwritten: false };
if (!isValidFilePath(fn) || !isScriptFilename(fn)) {
return ret;
writeToScriptFile(filename: string, code: string): writeResult {
if (!isValidFilePath(filename) || !isScriptFilename(filename)) {
return { success: false, overwritten: false };
}
// Check if the script already exists, and overwrite it if it does
for (let i = 0; i < this.scripts.length; ++i) {
if (fn === this.scripts[i].filename) {
const script = this.scripts[i];
script.code = code;
// Set ramUsage to null in order to force recalculation on next run
script.invalidateModule();
ret.overwritten = true;
ret.success = true;
return ret;
}
const script = this.scripts.get(filename);
if (script) {
script.invalidateModule();
script.code = code;
return { success: true, overwritten: true };
}
// Otherwise, create a new script
const newScript = new Script(fn, code, this.hostname);
this.scripts.push(newScript);
ret.success = true;
return ret;
const newScript = new Script(filename, code, this.hostname);
this.scripts.set(filename, newScript);
return { success: true, overwritten: false };
}
// Write to a text file

@ -6,12 +6,13 @@ import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers";
import { createRandomString } from "../utils/helpers/createRandomString";
import { createRandomIp } from "../utils/IPAddress";
import { Generic_fromJSON, Generic_toJSON, IReviverValue, constructorsForReviver } from "../utils/JSONReviver";
import { IPAddress, ServerName } from "../Types/strings";
export interface IConstructorParams {
adminRights?: boolean;
hackDifficulty?: number;
hostname: string;
ip?: string;
ip?: IPAddress;
isConnectedTo?: boolean;
maxRam?: number;
moneyAvailable?: number;
@ -60,7 +61,7 @@ export class Server extends BaseServer {
// "hacknet-node-X" hostnames are reserved for Hacknet Servers
if (this.hostname.startsWith("hacknet-node-") || this.hostname.startsWith("hacknet-server-")) {
this.hostname = createRandomString(10);
this.hostname = createRandomString(10) as ServerName;
}
this.purchasedByPlayer = params.purchasedByPlayer != null ? params.purchasedByPlayer : false;

@ -143,10 +143,13 @@ export function SidebarRoot(props: IProps): React.ReactElement {
Player.factions.length > 0 ||
Player.augmentations.length > 0 ||
Player.queuedAugmentations.length > 0 ||
Player.sourceFiles.length > 0;
Player.sourceFiles.size > 0;
const canOpenAugmentations =
Player.augmentations.length > 0 || Player.queuedAugmentations.length > 0 || Player.sourceFiles.length > 0;
Player.augmentations.length > 0 ||
Player.queuedAugmentations.length > 0 ||
Player.sourceFiles.size > 0 ||
Player.exploits.length > 0;
const canOpenSleeves = Player.sleeves.length > 0;

@ -1,12 +0,0 @@
export class PlayerOwnedSourceFile {
// Source-File level
lvl = 1;
// Source-File number
n = 1;
constructor(n: number, level: number) {
this.n = n;
this.lvl = level;
}
}

@ -1,21 +1,20 @@
import { PlayerOwnedSourceFile } from "./PlayerOwnedSourceFile";
import { SourceFiles } from "./SourceFiles";
import { Player } from "@player";
export function applySourceFile(srcFile: PlayerOwnedSourceFile): void {
const srcFileKey = "SourceFile" + srcFile.n;
export function applySourceFile(bn: number, lvl: number): void {
const srcFileKey = "SourceFile" + bn;
const sourceFileObject = SourceFiles[srcFileKey];
if (sourceFileObject == null) {
console.error(`Invalid source file number: ${srcFile.n}`);
console.error(`Invalid source file number: ${bn}`);
return;
}
switch (srcFile.n) {
switch (bn) {
case 1: {
// The Source Genesis
let mult = 0;
for (let i = 0; i < srcFile.lvl; ++i) {
for (let i = 0; i < lvl; ++i) {
mult += 16 / Math.pow(2, i);
}
const incMult = 1 + mult / 100;
@ -51,7 +50,7 @@ export function applySourceFile(srcFile: PlayerOwnedSourceFile): void {
case 2: {
// Rise of the Underworld
let mult = 0;
for (let i = 0; i < srcFile.lvl; ++i) {
for (let i = 0; i < lvl; ++i) {
mult += 24 / Math.pow(2, i);
}
const incMult = 1 + mult / 100;
@ -63,7 +62,7 @@ export function applySourceFile(srcFile: PlayerOwnedSourceFile): void {
case 3: {
// Corporatocracy
let mult = 0;
for (let i = 0; i < srcFile.lvl; ++i) {
for (let i = 0; i < lvl; ++i) {
mult += 8 / Math.pow(2, i);
}
const incMult = 1 + mult / 100;
@ -79,7 +78,7 @@ export function applySourceFile(srcFile: PlayerOwnedSourceFile): void {
case 5: {
// Artificial Intelligence
let mult = 0;
for (let i = 0; i < srcFile.lvl; ++i) {
for (let i = 0; i < lvl; ++i) {
mult += 8 / Math.pow(2, i);
}
const incMult = 1 + mult / 100;
@ -94,7 +93,7 @@ export function applySourceFile(srcFile: PlayerOwnedSourceFile): void {
case 6: {
// Bladeburner
let mult = 0;
for (let i = 0; i < srcFile.lvl; ++i) {
for (let i = 0; i < lvl; ++i) {
mult += 8 / Math.pow(2, i);
}
const incMult = 1 + mult / 100;
@ -111,7 +110,7 @@ export function applySourceFile(srcFile: PlayerOwnedSourceFile): void {
case 7: {
// Bladeburner 2079
let mult = 0;
for (let i = 0; i < srcFile.lvl; ++i) {
for (let i = 0; i < lvl; ++i) {
mult += 8 / Math.pow(2, i);
}
const incMult = 1 + mult / 100;
@ -124,7 +123,7 @@ export function applySourceFile(srcFile: PlayerOwnedSourceFile): void {
case 8: {
// Ghost of Wall Street
let mult = 0;
for (let i = 0; i < srcFile.lvl; ++i) {
for (let i = 0; i < lvl; ++i) {
mult += 12 / Math.pow(2, i);
}
const incMult = 1 + mult / 100;
@ -134,7 +133,7 @@ export function applySourceFile(srcFile: PlayerOwnedSourceFile): void {
case 9: {
// Hacktocracy
let mult = 0;
for (let i = 0; i < srcFile.lvl; ++i) {
for (let i = 0; i < lvl; ++i) {
mult += 12 / Math.pow(2, i);
}
const incMult = 1 + mult / 100;
@ -154,7 +153,7 @@ export function applySourceFile(srcFile: PlayerOwnedSourceFile): void {
case 11: {
// The Big Crash
let mult = 0;
for (let i = 0; i < srcFile.lvl; ++i) {
for (let i = 0; i < lvl; ++i) {
mult += 32 / Math.pow(2, i);
}
const incMult = 1 + mult / 100;
@ -169,7 +168,7 @@ export function applySourceFile(srcFile: PlayerOwnedSourceFile): void {
// Grants more space on Stanek's Gift.
break;
default:
console.error(`Invalid source file number: ${srcFile.n}`);
console.error(`Invalid source file number: ${bn}`);
break;
}

@ -41,8 +41,8 @@ export function getSubdirectories(serv: BaseServer, dir: string): string[] {
}
}
for (const script of serv.scripts) {
processFile(script.filename);
for (const scriptFilename of serv.scripts.keys()) {
processFile(scriptFilename);
}
for (const txt of serv.textFiles) {
@ -56,7 +56,7 @@ export function getSubdirectories(serv: BaseServer, dir: string): string[] {
export function containsFiles(server: BaseServer, dir: string): boolean {
const dirWithTrailingSlash = dir + (dir.slice(-1) === "/" ? "" : "/");
return [...server.scripts.map((s) => s.filename), ...server.textFiles.map((t) => t.fn)].some((filename) =>
return [...server.scripts.keys(), ...server.textFiles.map((t) => t.fn)].some((filename) =>
filename.startsWith(dirWithTrailingSlash),
);
}

@ -400,16 +400,10 @@ export class Terminal {
}
getScript(filename: string): Script | null {
const s = Player.getCurrentServer();
const server = Player.getCurrentServer();
const filepath = this.getFilepath(filename);
if (!filepath) return null;
for (const script of s.scripts) {
if (filepath === script.filename) {
return script;
}
}
return null;
if (filepath === null) return null;
return server.scripts.get(filepath) ?? null;
}
getTextFile(filename: string): TextFile | null {

@ -7,6 +7,7 @@ import { isScriptFilename } from "../../../Script/isScriptFilename";
import { CursorPositions } from "../../../ScriptEditor/CursorPositions";
import { Script } from "../../../Script/Script";
import { isEmpty } from "lodash";
import { ScriptFilename } from "src/Types/strings";
interface EditorParameters {
args: (string | number | boolean)[];
@ -28,7 +29,7 @@ interface ISimpleScriptGlob {
postGlob: string;
globError: string;
globMatches: string[];
globAgainst: Script[];
globAgainst: Map<ScriptFilename, Script>;
}
function containsSimpleGlob(filename: string): boolean {
@ -45,7 +46,7 @@ function detectSimpleScriptGlob({ args, server }: EditorParameters): ISimpleScri
return null;
}
function parseSimpleScriptGlob(globString: string, globDatabase: Script[]): ISimpleScriptGlob {
function parseSimpleScriptGlob(globString: string, globDatabase: Map<ScriptFilename, Script>): ISimpleScriptGlob {
const parsedGlob: ISimpleScriptGlob = {
glob: globString,
preGlob: "",

@ -56,17 +56,8 @@ export function cp(args: (string | number | boolean)[], server: BaseServer): voi
}
// Get the current script
let sourceScript = null;
for (let i = 0; i < server.scripts.length; ++i) {
if (areFilesEqual(server.scripts[i].filename, src)) {
sourceScript = server.scripts[i];
break;
}
}
if (sourceScript == null) {
Terminal.error("cp failed. No such script exists");
return;
}
const sourceScript = server.scripts.get(src);
if (!sourceScript) return Terminal.error("cp failed. No such script exists");
const sRes = server.writeToScriptFile(dst, sourceScript.code);
if (!sRes.success) {

@ -19,8 +19,8 @@ export function exportScripts(pattern: string, server: BaseServer): void {
// In the case of script files, we pull from the server.scripts array
if (!matchEnding || isScriptFilename(matchEnding))
zipFiles(
server.scripts.map((s) => s.filename),
server.scripts.map((s) => s.code),
[...server.scripts.keys()],
[...server.scripts.values()].map((script) => script.code),
);
// In the case of text files, we pull from the server.scripts array
if (!matchEnding || matchEnding.endsWith(".txt"))

@ -106,7 +106,7 @@ export function ls(args: (string | number | boolean)[], server: BaseServer): voi
// Get all of the programs and scripts on the machine into one temporary array
for (const program of server.programs) handleFn(program, allPrograms);
for (const script of server.scripts) handleFn(script.filename, allScripts);
for (const scriptFilename of server.scripts.keys()) handleFn(scriptFilename, allScripts);
for (const txt of server.textFiles) handleFn(txt.fn, allTextFiles);
for (const contract of server.contracts) handleFn(contract.fn, allContracts);
for (const msgOrLit of server.messages) handleFn(msgOrLit, allMessages);

@ -43,50 +43,43 @@ export function runScript(commandArgs: (string | number | boolean)[], server: Ba
}
// Check if the script exists and if it does run it
for (let i = 0; i < server.scripts.length; i++) {
if (server.scripts[i].filename !== scriptName) {
continue;
}
// Check for admin rights and that there is enough RAM available to run
const script = server.scripts[i];
script.server = server.hostname;
const singleRamUsage = script.getRamUsage(server.scripts);
if (!singleRamUsage) return Terminal.error("Error while calculating ram usage for this script.");
const ramUsage = singleRamUsage * numThreads;
const ramAvailable = server.maxRam - server.ramUsed;
const script = server.scripts.get(scriptName);
if (!script) return Terminal.error("No such script");
if (!server.hasAdminRights) {
Terminal.error("Need root access to run script");
return;
}
const singleRamUsage = script.getRamUsage(server.scripts);
if (!singleRamUsage) return Terminal.error("Error while calculating ram usage for this script.");
const ramUsage = singleRamUsage * numThreads;
const ramAvailable = server.maxRam - server.ramUsed;
if (ramUsage > ramAvailable + 0.001) {
Terminal.error(
"This machine does not have enough RAM to run this script" +
(numThreads === 1 ? "" : ` with ${numThreads} threads`) +
`. Script requires ${formatRam(ramUsage)} of RAM`,
);
return;
}
// Able to run script
const runningScript = new RunningScript(script, singleRamUsage, args);
runningScript.threads = numThreads;
const success = startWorkerScript(runningScript, server);
if (!success) {
Terminal.error(`Failed to start script`);
return;
}
Terminal.print(
`Running script with ${numThreads} thread(s), pid ${runningScript.pid} and args: ${JSON.stringify(args)}.`,
);
if (tailFlag) {
LogBoxEvents.emit(runningScript);
}
if (!server.hasAdminRights) {
Terminal.error("Need root access to run script");
return;
}
Terminal.error("No such script");
if (ramUsage > ramAvailable + 0.001) {
Terminal.error(
"This machine does not have enough RAM to run this script" +
(numThreads === 1 ? "" : ` with ${numThreads} threads`) +
`. Script requires ${formatRam(ramUsage)} of RAM`,
);
return;
}
// Able to run script
const runningScript = new RunningScript(script, singleRamUsage, args);
runningScript.threads = numThreads;
const success = startWorkerScript(runningScript, server);
if (!success) {
Terminal.error(`Failed to start script`);
return;
}
Terminal.print(
`Running script with ${numThreads} thread(s), pid ${runningScript.pid} and args: ${JSON.stringify(args)}.`,
);
if (tailFlag) {
LogBoxEvents.emit(runningScript);
}
return;
}

@ -51,11 +51,8 @@ export function scp(args: (string | number | boolean)[], server: BaseServer): vo
}
// Get the current script
const sourceScript = server.scripts.find((script) => script.filename === scriptname);
if (!sourceScript) {
Terminal.error("scp failed. No such script exists");
return;
}
const sourceScript = server.scripts.get(scriptname);
if (!sourceScript) return Terminal.error("scp failed. No such script exists");
const sRes = destServer.writeToScriptFile(scriptname, sourceScript.code);
if (!sRes.success) {

@ -101,8 +101,8 @@ export async function determineAllPossibilitiesForTabCompletion(
}
function addAllScripts(): void {
for (const script of currServ.scripts) {
const res = processFilepath(script.filename);
for (const scriptFilename of currServ.scripts.keys()) {
const res = processFilepath(scriptFilename);
if (res) {
allPos.push(res);
}
@ -281,10 +281,7 @@ export async function determineAllPossibilitiesForTabCompletion(
// Use regex to remove any leading './', and then check if it matches against
// the output of processFilepath or if it matches with a '/' prepended,
// this way autocomplete works inside of directories
const script = currServ.scripts.find((script) => {
const fn = filename.replace(/^\.\//g, "");
return processFilepath(script.filename) === fn || script.filename === "/" + fn;
});
const script = currServ.scripts.get(filename);
if (!script) return; // Doesn't exist.
let loadedModule;
try {
@ -304,7 +301,7 @@ export async function determineAllPossibilitiesForTabCompletion(
const flagFunc = Flags(flags._);
const autocompleteData: AutocompleteData = {
servers: GetAllServers().map((server) => server.hostname),
scripts: currServ.scripts.map((script) => script.filename),
scripts: [...currServ.scripts.keys()],
txts: currServ.textFiles.map((txt) => txt.fn),
flags: (schema: unknown) => {
if (!Array.isArray(schema)) throw new Error("flags require an array of array");
@ -334,8 +331,8 @@ export async function determineAllPossibilitiesForTabCompletion(
// invocation of `run`.
if (input.startsWith("./")) {
// All programs and scripts
for (const script of currServ.scripts) {
const res = processFilepath(script.filename);
for (const scriptFilename of currServ.scripts.keys()) {
const res = processFilepath(scriptFilename);
if (res) {
allPos.push(res);
}

20
src/Types/Jsonable.ts Normal file

@ -0,0 +1,20 @@
import type { IReviverValue } from "../utils/JSONReviver";
// Jsonable versions of builtin JS class objects
export class JSONSet<T> extends Set<T> {
toJSON(): IReviverValue {
return { ctor: "JSONSet", data: Array.from(this) };
}
static fromJSON(value: IReviverValue): JSONSet<any> {
return new JSONSet(value.data);
}
}
export class JSONMap<K, V> extends Map<K, V> {
toJSON(): IReviverValue {
return { ctor: "JSONMap", data: Array.from(this) };
}
static fromJSON(value: IReviverValue): JSONMap<any, any> {
return new JSONMap(value.data);
}
}

29
src/Types/strings.ts Normal file

@ -0,0 +1,29 @@
// Script Filename
export type ScriptFilename = string /*& { __type: "ScriptFilename" }*/;
/*export function isScriptFilename(value: string): value is ScriptFilename {
// implementation
}*/
/*export function sanitizeScriptFilename(filename: string): ScriptFilename {
// implementation
}*/
export function scriptFilenameFromImport(importPath: string, ns1?: boolean): ScriptFilename {
if (importPath.startsWith("./")) importPath = importPath.substring(2);
if (!ns1 && !importPath.endsWith(".js")) importPath += ".js";
if (ns1 && !importPath.endsWith(".script")) importPath += ".script";
return importPath as ScriptFilename;
}
// Server name
export type ServerName = string /*& { __type: "ServerName" }*/;
/*export function isExistingServerName(value: unknown): value is ServerName {
if (AllServers.has(value as ServerName)) return true; // For AllServers as an exported map
return false;
}*/
// IP Address
export type IPAddress = string /*& { __type: "IPAddress" }*/;
export function isIPAddress(value: string): value is IPAddress {
const regex =
/^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
return regex.test(value);
}

@ -255,21 +255,19 @@ const Engine: {
const contractChancesWhileOffline = Math.floor(timeOffline / (1000 * 60 * 10));
// Generate coding contracts
if (Player.sourceFiles.length > 0) {
let numContracts = 0;
if (contractChancesWhileOffline > 100) {
numContracts += Math.floor(contractChancesWhileOffline * 0.25);
}
if (contractChancesWhileOffline > 0 && contractChancesWhileOffline <= 100) {
for (let i = 0; i < contractChancesWhileOffline; ++i) {
if (Math.random() <= 0.25) {
numContracts++;
}
let numContracts = 0;
if (contractChancesWhileOffline > 100) {
numContracts += Math.floor(contractChancesWhileOffline * 0.25);
}
if (contractChancesWhileOffline > 0 && contractChancesWhileOffline <= 100) {
for (let i = 0; i < contractChancesWhileOffline; ++i) {
if (Math.random() <= 0.25) {
numContracts++;
}
}
for (let i = 0; i < numContracts; i++) {
generateRandomContract();
}
}
for (let i = 0; i < numContracts; i++) {
generateRandomContract();
}
let offlineReputation = 0;

@ -84,7 +84,7 @@ function MultiplierTable(props: MultTableProps): React.ReactElement {
}
function CurrentBitNode(): React.ReactElement {
if (Player.sourceFiles.length > 0) {
if (Player.sourceFiles.size > 0) {
const index = "BitNode" + Player.bitNodeN;
const lvl = Math.min(Player.sourceFileLvl(Player.bitNodeN) + 1, Player.bitNodeN === 12 ? Infinity : 3);
return (
@ -175,7 +175,7 @@ function MoneyModal({ open, onClose }: IMoneyModalProps): React.ReactElement {
{convertMoneySourceTrackerToString(Player.moneySourceA)}
</>
);
if (Player.sourceFiles.length !== 0) {
if (Player.sourceFiles.size > 0) {
content = (
<>
{content}
@ -205,7 +205,7 @@ export function CharacterStats(): React.ReactElement {
const timeRows = [
["Since last Augmentation installation", convertTimeMsToTimeElapsedString(Player.playtimeSinceLastAug)],
];
if (Player.sourceFiles.length > 0) {
if (Player.sourceFiles.size > 0) {
timeRows.push(["Since last Bitnode destroyed", convertTimeMsToTimeElapsedString(Player.playtimeSinceLastBitnode)]);
}
timeRows.push(["Total", convertTimeMsToTimeElapsedString(Player.totalPlaytime)]);

@ -20,7 +20,6 @@ import { Settings } from "../../Settings/Settings";
import { ANSIITypography } from "./ANSIITypography";
import { ScriptArg } from "../../Netscript/ScriptArg";
import { useRerender } from "./hooks";
import { areFilesEqual } from "../../Terminal/DirectoryHelpers";
import { dialogBoxCreate } from "./DialogBox";
let layerCounter = 0;
@ -222,7 +221,7 @@ function LogWindow(props: IProps): React.ReactElement {
if (server === null) return;
const s = findRunningScript(script.filename, script.args, server);
if (s === null) {
const baseScript = server.scripts.find((serverScript) => areFilesEqual(serverScript.filename, script.filename));
const baseScript = server.scripts.get(script.filename);
if (!baseScript) {
return dialogBoxCreate(
`Could not launch script. The script ${script.filename} no longer exists on the server ${server.hostname}.`,

@ -1,8 +1,9 @@
import { IPAddress } from "src/Types/strings";
import { getRandomByte } from "./helpers/getRandomByte";
/**
* Generate a random IP address
* Does not check to see if the IP already exists in the game
*/
export const createRandomIp = (): string =>
`${getRandomByte(99)}.${getRandomByte(9)}.${getRandomByte(9)}.${getRandomByte(9)}`;
export const createRandomIp = (): IPAddress =>
`${getRandomByte(99)}.${getRandomByte(9)}.${getRandomByte(9)}.${getRandomByte(9)}` as IPAddress;

@ -1,6 +1,11 @@
/* Generic Reviver, toJSON, and fromJSON functions used for saving and loading objects */
import { ObjectValidator, validateObject } from "./Validator";
import { JSONMap, JSONSet } from "../Types/Jsonable";
type JsonableClass = (new () => { toJSON: () => IReviverValue }) & {
fromJSON: (value: IReviverValue) => any;
validationData?: ObjectValidator<any>;
};
export interface IReviverValue {
ctor: string;
@ -38,15 +43,7 @@ export function Reviver(_key: string, value: unknown): any {
return obj;
}
export const constructorsForReviver: Partial<
Record<
string,
(new () => object) & {
fromJSON: (value: IReviverValue) => any;
validationData?: ObjectValidator<any>;
}
>
> = {};
export const constructorsForReviver: Partial<Record<string, JsonableClass>> = { JSONSet, JSONMap };
/**
* A generic "toJSON" function that creates the data expected by Reviver.

@ -1,11 +0,0 @@
/**
* Checks whether a IP Address string is valid.
* @param ipaddress A string representing a potential IP Address
*/
export function isValidIPAddress(ipaddress: string): boolean {
const byteRange = "(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)";
const regexStr = `^${byteRange}\.${byteRange}\.${byteRange}\.${byteRange}$`;
const ipAddressRegex = new RegExp(regexStr);
return ipAddressRegex.test(ipaddress);
}

@ -95,7 +95,11 @@ export function v1APIBreak(): void {
for (const server of GetAllServers()) {
for (const change of detect) {
const s: IFileLine[] = [];
for (const script of server.scripts) {
const scriptsArray: Script[] = Array.isArray(server.scripts)
? (server.scripts as Script[])
: [...server.scripts.values()];
for (const script of scriptsArray) {
const lines = script.code.split("\n");
for (let i = 0; i < lines.length; i++) {
if (lines[i].includes(change[0])) {
@ -121,7 +125,8 @@ export function v1APIBreak(): void {
home.writeToTextFile("v1_DETECTED_CHANGES.txt", txt);
}
for (const server of GetAllServers()) {
// API break function is called before version31 / 2.3.0 changes - scripts is still an array
for (const server of GetAllServers() as unknown as { scripts: Script[] }[]) {
const backups: Script[] = [];
for (const script of server.scripts) {
if (!hasChanges(script.code)) continue;

@ -227,7 +227,8 @@ export const v2APIBreak = () => {
});
}
for (const script of home.scripts) {
// API break function is called before the version31 2.3.0 changes, scripts are still an array.
for (const script of home.scripts as unknown as Script[]) {
processScript(rules, script);
}

@ -27,9 +27,8 @@ function isRemovedFunction(ctx: NetscriptContext, fn: (ctx: NetscriptContext) =>
}
describe("Netscript RAM Calculation/Generation Tests", function () {
Player.sourceFiles[0] = { n: 4, lvl: 3 };
Player.sourceFiles.set(4, 3);
// For simulating costs of singularity functions.
const sf4 = Player.sourceFiles[0];
const baseCost = RamCostConstants.Base;
const maxCost = RamCostConstants.Max;
const script = new Script();
@ -38,7 +37,7 @@ describe("Netscript RAM Calculation/Generation Tests", function () {
script.code = code;
// Force ram calculation reset
script.ramUsage = null;
const ramUsage = script.getRamUsage([]);
const ramUsage = script.getRamUsage(new Map());
if (!ramUsage) throw new Error("Ram usage should be defined.");
const runningScript = new RunningScript(script, ramUsage);
return runningScript;
@ -63,7 +62,7 @@ describe("Netscript RAM Calculation/Generation Tests", function () {
ramUsage: scriptRef.ramUsage,
scriptRef,
};
const nsExternal = NetscriptFunctions(workerScript as WorkerScript);
const nsExternal = NetscriptFunctions(workerScript as unknown as WorkerScript);
function combinedRamCheck(
fn: PotentiallyAsyncFunction,
@ -78,7 +77,7 @@ describe("Netscript RAM Calculation/Generation Tests", function () {
expect(getRamCost(...fnPath)).toEqual(expectedRamCost);
// Static ram check
const staticCost = calculateRamUsage(code, []).cost;
const staticCost = calculateRamUsage(code, new Map()).cost;
expect(staticCost).toBeCloseTo(Math.min(baseCost + expectedRamCost + extraLayerCost, maxCost));
// reset workerScript for dynamic check
@ -144,7 +143,7 @@ describe("Netscript RAM Calculation/Generation Tests", function () {
describe("Singularity multiplier checks", () => {
// Checks were already done above for SF4.3 having normal ramcost.
sf4.lvl = 3;
Player.sourceFiles.set(4, 3);
const lvlToMult = { 0: 16, 1: 16, 2: 4 };
const externalSingularity = nsExternal.singularity;
const ramCostSingularity = RamCosts.singularity;
@ -160,7 +159,7 @@ describe("Netscript RAM Calculation/Generation Tests", function () {
});
for (const lvl of [0, 1, 2] as const) {
it(`SF4.${lvl} check for x${lvlToMult[lvl]} costs`, () => {
sf4.lvl = lvl;
Player.sourceFiles.set(4, lvl);
const expectedMult = lvlToMult[lvl];
singObjects.forEach(({ name, baseRam }) => {
const fn = getFunction(externalSingularity[name]);

@ -5,6 +5,7 @@ import { RunningScript } from "../../../src/Script/RunningScript";
import { AddToAllServers, DeleteServer } from "../../../src/Server/AllServers";
import { WorkerScriptStartStopEventEmitter } from "../../../src/Netscript/WorkerScriptStartStopEventEmitter";
import { AlertEvents } from "../../../src/ui/React/AlertManager";
import type { Script } from "src/Script/Script";
// Replace Blob/ObjectURL functions, because they don't work natively in Jest
global.Blob = class extends Blob {
@ -89,10 +90,11 @@ test.each([
expect(server.writeToScriptFile(s.name, s.code)).toEqual({ success: true, overwritten: false });
}
const script = server.scripts[server.scripts.length - 1];
const script = server.scripts.get(scripts[scripts.length - 1].name) as Script;
expect(script.filename).toEqual(scripts[scripts.length - 1].name);
const ramUsage = script.getRamUsage(server.scripts);
if (!ramUsage) throw new Error(`ramUsage calculated to be ${ramUsage}`);
const runningScript = new RunningScript(script, ramUsage as number);
expect(startWorkerScript(runningScript, server)).toBeGreaterThan(0);
// We don't care about start, so subscribe after that. Await script death.

@ -21,7 +21,7 @@ describe("Parsing NetScript code to work out static RAM costs", function () {
const code = `
export async function main(ns) { }
`;
const calculated = calculateRamUsage(code, []).cost;
const calculated = calculateRamUsage(code, new Map()).cost;
expectCost(calculated, 0);
});
@ -31,7 +31,7 @@ describe("Parsing NetScript code to work out static RAM costs", function () {
ns.print("Slum snakes r00l!");
}
`;
const calculated = calculateRamUsage(code, []).cost;
const calculated = calculateRamUsage(code, new Map()).cost;
expectCost(calculated, 0);
});
@ -41,7 +41,7 @@ describe("Parsing NetScript code to work out static RAM costs", function () {
await ns.hack("joesguns");
}
`;
const calculated = calculateRamUsage(code, []).cost;
const calculated = calculateRamUsage(code, new Map()).cost;
expectCost(calculated, HackCost);
});
@ -51,7 +51,7 @@ describe("Parsing NetScript code to work out static RAM costs", function () {
await X.hack("joesguns");
}
`;
const calculated = calculateRamUsage(code, []).cost;
const calculated = calculateRamUsage(code, new Map()).cost;
expectCost(calculated, HackCost);
});
@ -62,7 +62,7 @@ describe("Parsing NetScript code to work out static RAM costs", function () {
await ns.hack("joesguns");
}
`;
const calculated = calculateRamUsage(code, []).cost;
const calculated = calculateRamUsage(code, new Map()).cost;
expectCost(calculated, HackCost);
});
@ -73,7 +73,7 @@ describe("Parsing NetScript code to work out static RAM costs", function () {
await ns.grow("joesguns");
}
`;
const calculated = calculateRamUsage(code, []).cost;
const calculated = calculateRamUsage(code, new Map()).cost;
expectCost(calculated, HackCost + GrowCost);
});
@ -86,7 +86,7 @@ describe("Parsing NetScript code to work out static RAM costs", function () {
await ns.hack("joesguns");
}
`;
const calculated = calculateRamUsage(code, []).cost;
const calculated = calculateRamUsage(code, new Map()).cost;
expectCost(calculated, HackCost);
});
@ -101,7 +101,7 @@ describe("Parsing NetScript code to work out static RAM costs", function () {
async doHacking() { await this.ns.hack("joesguns"); }
}
`;
const calculated = calculateRamUsage(code, []).cost;
const calculated = calculateRamUsage(code, new Map()).cost;
expectCost(calculated, HackCost);
});
@ -116,7 +116,7 @@ describe("Parsing NetScript code to work out static RAM costs", function () {
async doHacking() { await this.#ns.hack("joesguns"); }
}
`;
const calculated = calculateRamUsage(code, []).cost;
const calculated = calculateRamUsage(code, new Map()).cost;
expectCost(calculated, HackCost);
});
});
@ -129,7 +129,7 @@ describe("Parsing NetScript code to work out static RAM costs", function () {
}
function get() { return 0; }
`;
const calculated = calculateRamUsage(code, []).cost;
const calculated = calculateRamUsage(code, new Map()).cost;
expectCost(calculated, 0);
});
@ -140,7 +140,7 @@ describe("Parsing NetScript code to work out static RAM costs", function () {
}
function purchaseNode() { return 0; }
`;
const calculated = calculateRamUsage(code, []).cost;
const calculated = calculateRamUsage(code, new Map()).cost;
// Works at present, because the parser checks the namespace only, not the function name
expectCost(calculated, 0);
});
@ -153,7 +153,7 @@ describe("Parsing NetScript code to work out static RAM costs", function () {
}
function getTask() { return 0; }
`;
const calculated = calculateRamUsage(code, []).cost;
const calculated = calculateRamUsage(code, new Map()).cost;
expectCost(calculated, 0);
});
});
@ -165,7 +165,7 @@ describe("Parsing NetScript code to work out static RAM costs", function () {
ns.hacknet.purchaseNode(0);
}
`;
const calculated = calculateRamUsage(code, []).cost;
const calculated = calculateRamUsage(code, new Map()).cost;
expectCost(calculated, HacknetCost);
});
@ -175,7 +175,7 @@ describe("Parsing NetScript code to work out static RAM costs", function () {
ns.corporation.getCorporation();
}
`;
const calculated = calculateRamUsage(code, []).cost;
const calculated = calculateRamUsage(code, new Map()).cost;
expectCost(calculated, MaxCost);
});
@ -186,7 +186,7 @@ describe("Parsing NetScript code to work out static RAM costs", function () {
ns.corporation.getCorporation();
}
`;
const calculated = calculateRamUsage(code, []).cost;
const calculated = calculateRamUsage(code, new Map()).cost;
expectCost(calculated, MaxCost);
});
@ -196,7 +196,7 @@ describe("Parsing NetScript code to work out static RAM costs", function () {
ns.sleeve.getTask(3);
}
`;
const calculated = calculateRamUsage(code, []).cost;
const calculated = calculateRamUsage(code, new Map()).cost;
expectCost(calculated, SleeveGetTaskCost);
});
});
@ -214,7 +214,7 @@ describe("Parsing NetScript code to work out static RAM costs", function () {
dummy();
}
`;
const calculated = calculateRamUsage(code, [lib]).cost;
const calculated = calculateRamUsage(code, new Map([["libTest.js", lib]])).cost;
expectCost(calculated, 0);
});
@ -230,7 +230,7 @@ describe("Parsing NetScript code to work out static RAM costs", function () {
await doHack(ns);
}
`;
const calculated = calculateRamUsage(code, [lib]).cost;
const calculated = calculateRamUsage(code, new Map([["libTest.js", lib]])).cost;
expectCost(calculated, HackCost);
});
@ -247,7 +247,7 @@ describe("Parsing NetScript code to work out static RAM costs", function () {
await doHack(ns);
}
`;
const calculated = calculateRamUsage(code, [lib]).cost;
const calculated = calculateRamUsage(code, new Map([["libTest.js", lib]])).cost;
expectCost(calculated, HackCost);
});
@ -264,7 +264,7 @@ describe("Parsing NetScript code to work out static RAM costs", function () {
await test.doHack(ns);
}
`;
const calculated = calculateRamUsage(code, [lib]).cost;
const calculated = calculateRamUsage(code, new Map([["libTest.js", lib]])).cost;
expectCost(calculated, HackCost + GrowCost);
});
@ -286,7 +286,7 @@ describe("Parsing NetScript code to work out static RAM costs", function () {
await test.doHack(ns);
}
`;
const calculated = calculateRamUsage(code, [lib]).cost;
const calculated = calculateRamUsage(code, new Map([["libTest.js", lib]])).cost;
expectCost(calculated, HackCost);
});
@ -312,7 +312,7 @@ describe("Parsing NetScript code to work out static RAM costs", function () {
await growerInstance.doGrow();
}
`;
const calculated = calculateRamUsage(code, [lib]).cost;
const calculated = calculateRamUsage(code, new Map([["libTest.js", lib]])).cost;
expectCost(calculated, GrowCost);
});
});

@ -89,7 +89,10 @@ exports[`load/saveAllServers 1`] = `
"programs": [],
"ramUsed": 0,
"runningScripts": [],
"scripts": [],
"scripts": {
"ctor": "JSONMap",
"data": []
},
"serversOnNetwork": [
"home"
],
@ -183,7 +186,10 @@ exports[`load/saveAllServers pruning RunningScripts 1`] = `
"programs": [],
"ramUsed": 0,
"runningScripts": [],
"scripts": [],
"scripts": {
"ctor": "JSONMap",
"data": []
},
"serversOnNetwork": [
"home"
],