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 { 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 { 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 // eslint-disable-next-line @typescript-eslint/no-unused-vars
@ -274,7 +274,7 @@ export const achievements: Record<string, Achievement> = {
NS2: { NS2: {
...achievementData["NS2"], ...achievementData["NS2"],
Icon: "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: { FROZE: {
...achievementData["FROZE"], ...achievementData["FROZE"],
@ -317,7 +317,7 @@ export const achievements: Record<string, Achievement> = {
SCRIPTS_30: { SCRIPTS_30: {
...achievementData["SCRIPTS_30"], ...achievementData["SCRIPTS_30"],
Icon: "folders", Icon: "folders",
Condition: () => Player.getHomeComputer().scripts.length >= 30, Condition: () => Player.getHomeComputer().scripts.size >= 30,
}, },
KARMA_1000000: { KARMA_1000000: {
...achievementData["KARMA_1000000"], ...achievementData["KARMA_1000000"],
@ -342,7 +342,7 @@ export const achievements: Record<string, Achievement> = {
SCRIPT_32GB: { SCRIPT_32GB: {
...achievementData["SCRIPT_32GB"], ...achievementData["SCRIPT_32GB"],
Icon: "bigcost", 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: { FIRST_HACKNET_NODE: {
...achievementData["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 { export function SourceFilesElement(): React.ReactElement {
const sourceSfs = Player.sourceFiles.slice(); const sourceFilesCopy = new Map(Player.sourceFiles);
const exploits = Player.exploits; const exploits = Player.exploits;
// Create a fake SF for -1, if "owned" // Create a fake SF for -1, if "owned"
if (exploits.length > 0) { if (exploits.length > 0) {
sourceSfs.unshift({ sourceFilesCopy.set(-1, exploits.length);
n: -1,
lvl: exploits.length,
});
} }
const sfList = [...sourceFilesCopy];
if (Settings.OwnedAugmentationsOrder === OwnedAugmentationsOrderSetting.Alphabetically) { if (Settings.OwnedAugmentationsOrder === OwnedAugmentationsOrderSetting.Alphabetically) {
sourceSfs.sort((sf1, sf2) => { sfList.sort(([n1, __lvl1], [n2, __lvl2]) => n1 - n2);
return sf1.n - sf2.n;
});
} }
if (sourceSfs.length === 0) { if (sfList.length === 0) {
return <></>; return <></>;
} }
const [selectedSf, setSelectedSf] = useState(sourceSfs[0]); const firstEle = sfList[0];
const [selectedSf, setSelectedSf] = useState({ n: firstEle[0], lvl: firstEle[1] });
return ( return (
<Box sx={{ width: "100%", mt: 1 }}> <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}` }} sx={{ height: 400, overflowY: "scroll", borderRight: `1px solid ${Settings.theme.welllight}` }}
disablePadding disablePadding
> >
{sourceSfs.map((e, i) => { {sfList.map(([n, lvl], i) => {
const sfObj = safeGetSf(e.n); const sfObj = safeGetSf(n);
if (!sfObj) return; if (!sfObj) return;
const maxLevel = getMaxLevel(sfObj); const maxLevel = getMaxLevel(sfObj);
@ -113,8 +110,8 @@ export function SourceFilesElement(): React.ReactElement {
return ( return (
<ListItemButton <ListItemButton
key={i + 1} key={i + 1}
onClick={() => setSelectedSf(e)} onClick={() => setSelectedSf({ n, lvl })}
selected={selectedSf.n === e.n} selected={selectedSf.n === n}
sx={{ py: 0 }} sx={{ py: 0 }}
> >
<ListItemText <ListItemText
@ -122,7 +119,7 @@ export function SourceFilesElement(): React.ReactElement {
primary={<Typography>{sfObj.name}</Typography>} primary={<Typography>{sfObj.name}</Typography>}
secondary={ secondary={
<Typography> <Typography>
Level {e.lvl} / {maxLevel} Level {lvl} / {maxLevel}
</Typography> </Typography>
} }
/> />

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

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

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

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

@ -80,7 +80,7 @@ function checkForMessagesToSend(): void {
} }
//If the daemon can be hacked, send the player icarus.msg //If the daemon can be hacked, send the player icarus.msg
if (Player.skills.hacking >= worldDaemon.requiredHackingSkill) { 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. //If the daemon cannot be hacked, send the player truthgazer.msg a single time.
else if (!recvd(truthGazer)) { else if (!recvd(truthGazer)) {

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

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

@ -15,7 +15,7 @@ export function NetscriptBladeburner(): InternalAPI<INetscriptBladeburner> {
return; return;
}; };
const getBladeburner = function (ctx: NetscriptContext): Bladeburner { 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) { if (!apiAccess) {
throw helpers.makeRuntimeErrorMsg(ctx, "You have not unlocked the bladeburner API.", "API ACCESS"); throw helpers.makeRuntimeErrorMsg(ctx, "You have not unlocked the bladeburner API.", "API ACCESS");
} }

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

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

@ -7,7 +7,8 @@ import { parse } from "acorn";
import { LoadedModule, ScriptURL, ScriptModule } from "./Script/LoadedModule"; import { LoadedModule, ScriptURL, ScriptModule } from "./Script/LoadedModule";
import { Script } from "./Script/Script"; 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. // Acorn type def is straight up incomplete so we have to fill with our own.
export type Node = any; 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 // Return the module if it already exists
if (script.mod) return script.mod.module; 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 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. * @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 // Early return for recursive calls where the script already has a URL
if (script.mod) { if (script.mod) {
addDependencyInfo(script, seenStack); addDependencyInfo(script, seenStack);
@ -124,10 +125,10 @@ function generateLoadedModule(script: Script, scripts: Script[], seenStack: Scri
let newCode = script.code; let newCode = script.code;
// Loop through each node and replace the script name with a blob url. // Loop through each node and replace the script name with a blob url.
for (const node of importNodes) { 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. // Find the corresponding script.
const importedScript = scripts.find((s) => areImportsEquals(s.filename, filename)); const importedScript = scripts.get(filename);
if (!importedScript) continue; if (!importedScript) continue;
seenStack.push(script); seenStack.push(script);

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

@ -8,7 +8,6 @@ import * as workMethods from "./PlayerObjectWorkMethods";
import { setPlayer } from "../../Player"; import { setPlayer } from "../../Player";
import { Sleeve } from "../Sleeve/Sleeve"; import { Sleeve } from "../Sleeve/Sleeve";
import { PlayerOwnedSourceFile } from "../../SourceFile/PlayerOwnedSourceFile";
import { Exploit } from "../../Exploits/Exploit"; import { Exploit } from "../../Exploits/Exploit";
import { LocationName } from "../../Enums"; import { LocationName } from "../../Enums";
@ -21,6 +20,7 @@ import { HashManager } from "../../Hacknet/HashManager";
import { MoneySourceTracker } from "../../utils/MoneySourceTracker"; import { MoneySourceTracker } from "../../utils/MoneySourceTracker";
import { constructorsForReviver, Generic_toJSON, Generic_fromJSON, IReviverValue } from "../../utils/JSONReviver"; import { constructorsForReviver, Generic_toJSON, Generic_fromJSON, IReviverValue } from "../../utils/JSONReviver";
import { JSONMap } from "../../Types/Jsonable";
import { PlayerAchievement } from "../../Achievements/Achievements"; import { PlayerAchievement } from "../../Achievements/Achievements";
import { cyrb53 } from "../../utils/StringHelperFunctions"; import { cyrb53 } from "../../utils/StringHelperFunctions";
import { getRandomInt } from "../../utils/helpers/getRandomInt"; import { getRandomInt } from "../../utils/helpers/getRandomInt";
@ -59,7 +59,7 @@ export class PlayerObject extends Person implements IPlayer {
scriptProdSinceLastAug = 0; scriptProdSinceLastAug = 0;
sleeves: Sleeve[] = []; sleeves: Sleeve[] = [];
sleevesFromCovenant = 0; sleevesFromCovenant = 0;
sourceFiles: PlayerOwnedSourceFile[] = []; sourceFiles: JSONMap<number, number> = new JSONMap();
exploits: Exploit[] = []; exploits: Exploit[] = [];
achievements: PlayerAchievement[] = []; achievements: PlayerAchievement[] = [];
terminalCommandHistory: string[] = []; terminalCommandHistory: string[] = [];
@ -168,9 +168,15 @@ export class PlayerObject extends Person implements IPlayer {
/** Initializes a PlayerObject object from a JSON save state. */ /** Initializes a PlayerObject object from a JSON save state. */
static fromJSON(value: IReviverValue): PlayerObject { 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); 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; 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 //Will always be called after reapplyAllAugmentations() so multipliers do not have to be reset
//this.resetMultipliers(); //this.resetMultipliers();
for (let i = 0; i < this.sourceFiles.length; ++i) { for (const [bn, lvl] of this.sourceFiles) {
const srcFileKey = "SourceFile" + this.sourceFiles[i].n; const srcFileKey = "SourceFile" + bn;
const sourceFileObject = SourceFiles[srcFileKey]; const sourceFileObject = SourceFiles[srcFileKey];
if (sourceFileObject == null) { if (!sourceFileObject) {
console.error(`Invalid source file number: ${this.sourceFiles[i].n}`); console.error(`Invalid source file number: ${bn}`);
continue; continue;
} }
applySourceFile(this.sourceFiles[i]); applySourceFile(bn, lvl);
} }
applyExploit(); applyExploit();
this.updateSkillLevels(); this.updateSkillLevels();
@ -1222,9 +1222,7 @@ export function canAccessCotMG(this: PlayerObject): boolean {
} }
export function sourceFileLvl(this: PlayerObject, n: number): number { export function sourceFileLvl(this: PlayerObject, n: number): number {
const sf = this.sourceFiles.find((sf) => sf.n === n); return this.sourceFiles.get(n) ?? 0;
if (!sf) return 0;
return sf.lvl;
} }
export function focusPenalty(this: PlayerObject): number { export function focusPenalty(this: PlayerObject): number {

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

@ -192,7 +192,7 @@ export function prestigeSourceFile(flume: boolean): void {
AddToAllServers(homeComp); AddToAllServers(homeComp);
prestigeHomeComputer(homeComp); prestigeHomeComputer(homeComp);
// Ram usage needs to be cleared for bitnode-level resets, due to possible change in singularity cost. // 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 // Re-create foreign servers
initForeignServers(Player.getHomeComputer()); initForeignServers(Player.getHomeComputer());

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

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

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

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

@ -10,6 +10,7 @@ import { LoadedModule, ScriptURL } from "./LoadedModule";
import { Generic_fromJSON, Generic_toJSON, IReviverValue, constructorsForReviver } from "../utils/JSONReviver"; import { Generic_fromJSON, Generic_toJSON, IReviverValue, constructorsForReviver } from "../utils/JSONReviver";
import { roundToTwo } from "../utils/helpers/roundToTwo"; import { roundToTwo } from "../utils/helpers/roundToTwo";
import { RamCostConstants } from "../Netscript/RamCostGenerator"; import { RamCostConstants } from "../Netscript/RamCostGenerator";
import { ScriptFilename } from "src/Types/strings";
export class Script { export class Script {
code: string; code: string;
@ -81,7 +82,7 @@ export class Script {
} }
/** Gets the ram usage, while also attempting to update it if it's currently null */ /** 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; if (this.ramUsage) return this.ramUsage;
this.updateRamUsage(otherScripts); this.updateRamUsage(otherScripts);
return this.ramUsage; return this.ramUsage;
@ -91,8 +92,8 @@ export class Script {
* Calculates and updates the script's RAM usage based on its code * Calculates and updates the script's RAM usage based on its code
* @param {Script[]} otherScripts - Other scripts on the server. Used to process imports * @param {Script[]} otherScripts - Other scripts on the server. Used to process imports
*/ */
updateRamUsage(otherScripts: Script[]): void { updateRamUsage(otherScripts: Map<ScriptFilename, Script>): void {
const ramCalc = calculateRamUsage(this.code, otherScripts); const ramCalc = calculateRamUsage(this.code, otherScripts, this.filename.endsWith(".script"));
if (ramCalc.cost >= RamCostConstants.Base) { if (ramCalc.cost >= RamCostConstants.Base) {
this.ramUsage = roundToTwo(ramCalc.cost); this.ramUsage = roundToTwo(ramCalc.cost);
this.ramUsageEntries = ramCalc.entries as RamUsageEntry[]; 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 (server === null) throw new Error("Server should not be null but it is.");
if (isScriptFilename(scriptToSave.fileName)) { if (isScriptFilename(scriptToSave.fileName)) {
//If the current script already exists on the server, overwrite it //If the current script already exists on the server, overwrite it
for (let i = 0; i < server.scripts.length; i++) { const existingScript = server.scripts.get(scriptToSave.fileName);
if (scriptToSave.fileName == server.scripts[i].filename) { if (existingScript) {
server.scripts[i].saveScript(scriptToSave.fileName, scriptToSave.code, Player.currentServer); existingScript.saveScript(scriptToSave.fileName, scriptToSave.code, Player.currentServer);
if (Settings.SaveGameOnFileSave) saveObject.saveGame(); if (Settings.SaveGameOnFileSave) saveObject.saveGame();
Router.toPage(Page.Terminal); Router.toPage(Page.Terminal);
return; return;
}
} }
//If the current script does NOT exist, create a new one //If the current script does NOT exist, create a new one
const script = new Script(); const script = new Script();
script.saveScript(scriptToSave.fileName, scriptToSave.code, Player.currentServer); script.saveScript(scriptToSave.fileName, scriptToSave.code, Player.currentServer);
server.scripts.push(script); server.scripts.set(scriptToSave.fileName, script);
} else if (scriptToSave.isTxt) { } else if (scriptToSave.isTxt) {
for (let i = 0; i < server.textFiles.length; ++i) { for (let i = 0; i < server.textFiles.length; ++i) {
if (server.textFiles[i].fn === scriptToSave.fileName) { 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 (server === null) throw new Error("Server should not be null but it is.");
if (isScriptFilename(currentScript.fileName)) { if (isScriptFilename(currentScript.fileName)) {
//If the current script already exists on the server, overwrite it //If the current script already exists on the server, overwrite it
for (let i = 0; i < server.scripts.length; i++) { const existingScript = server.scripts.get(currentScript.fileName);
if (currentScript.fileName == server.scripts[i].filename) { if (existingScript) {
server.scripts[i].saveScript(currentScript.fileName, currentScript.code, Player.currentServer); existingScript.saveScript(currentScript.fileName, currentScript.code, Player.currentServer);
if (Settings.SaveGameOnFileSave) saveObject.saveGame(); if (Settings.SaveGameOnFileSave) saveObject.saveGame();
rerender(); rerender();
return; return;
}
} }
//If the current script does NOT exist, create a new one //If the current script does NOT exist, create a new one
const script = new Script(); const script = new Script();
script.saveScript(currentScript.fileName, currentScript.code, Player.currentServer); script.saveScript(currentScript.fileName, currentScript.code, Player.currentServer);
server.scripts.push(script); server.scripts.set(currentScript.fileName, script);
} else if (currentScript.isTxt) { } else if (currentScript.isTxt) {
for (let i = 0; i < server.textFiles.length; ++i) { for (let i = 0; i < server.textFiles.length; ++i) {
if (server.textFiles[i].fn === currentScript.fileName) { 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.`); if (server === null) throw new Error(`Server '${openScript.hostname}' should not be null, but it is.`);
const data = openScript.isTxt const data = openScript.isTxt
? server.textFiles.find((t) => t.filename === openScript.fileName)?.text ? 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; return data ?? null;
} }
function handleFilterChange(event: React.ChangeEvent<HTMLInputElement>): void { function handleFilterChange(event: React.ChangeEvent<HTMLInputElement>): void {

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

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

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

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

@ -41,8 +41,8 @@ export function getSubdirectories(serv: BaseServer, dir: string): string[] {
} }
} }
for (const script of serv.scripts) { for (const scriptFilename of serv.scripts.keys()) {
processFile(script.filename); processFile(scriptFilename);
} }
for (const txt of serv.textFiles) { 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 { export function containsFiles(server: BaseServer, dir: string): boolean {
const dirWithTrailingSlash = dir + (dir.slice(-1) === "/" ? "" : "/"); 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), filename.startsWith(dirWithTrailingSlash),
); );
} }

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

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

@ -56,17 +56,8 @@ export function cp(args: (string | number | boolean)[], server: BaseServer): voi
} }
// Get the current script // Get the current script
let sourceScript = null; const sourceScript = server.scripts.get(src);
for (let i = 0; i < server.scripts.length; ++i) { if (!sourceScript) return Terminal.error("cp failed. No such script exists");
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 sRes = server.writeToScriptFile(dst, sourceScript.code); const sRes = server.writeToScriptFile(dst, sourceScript.code);
if (!sRes.success) { 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 // In the case of script files, we pull from the server.scripts array
if (!matchEnding || isScriptFilename(matchEnding)) if (!matchEnding || isScriptFilename(matchEnding))
zipFiles( zipFiles(
server.scripts.map((s) => s.filename), [...server.scripts.keys()],
server.scripts.map((s) => s.code), [...server.scripts.values()].map((script) => script.code),
); );
// In the case of text files, we pull from the server.scripts array // In the case of text files, we pull from the server.scripts array
if (!matchEnding || matchEnding.endsWith(".txt")) 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 // 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 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 txt of server.textFiles) handleFn(txt.fn, allTextFiles);
for (const contract of server.contracts) handleFn(contract.fn, allContracts); for (const contract of server.contracts) handleFn(contract.fn, allContracts);
for (const msgOrLit of server.messages) handleFn(msgOrLit, allMessages); 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 // Check if the script exists and if it does run it
for (let i = 0; i < server.scripts.length; i++) { const script = server.scripts.get(scriptName);
if (server.scripts[i].filename !== scriptName) { if (!script) return Terminal.error("No such script");
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;
if (!server.hasAdminRights) { const singleRamUsage = script.getRamUsage(server.scripts);
Terminal.error("Need root access to run script"); if (!singleRamUsage) return Terminal.error("Error while calculating ram usage for this script.");
return; const ramUsage = singleRamUsage * numThreads;
} const ramAvailable = server.maxRam - server.ramUsed;
if (ramUsage > ramAvailable + 0.001) { if (!server.hasAdminRights) {
Terminal.error( Terminal.error("Need root access to run script");
"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; 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 // Get the current script
const sourceScript = server.scripts.find((script) => script.filename === scriptname); const sourceScript = server.scripts.get(scriptname);
if (!sourceScript) { if (!sourceScript) return Terminal.error("scp failed. No such script exists");
Terminal.error("scp failed. No such script exists");
return;
}
const sRes = destServer.writeToScriptFile(scriptname, sourceScript.code); const sRes = destServer.writeToScriptFile(scriptname, sourceScript.code);
if (!sRes.success) { if (!sRes.success) {

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

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

@ -20,7 +20,6 @@ import { Settings } from "../../Settings/Settings";
import { ANSIITypography } from "./ANSIITypography"; import { ANSIITypography } from "./ANSIITypography";
import { ScriptArg } from "../../Netscript/ScriptArg"; import { ScriptArg } from "../../Netscript/ScriptArg";
import { useRerender } from "./hooks"; import { useRerender } from "./hooks";
import { areFilesEqual } from "../../Terminal/DirectoryHelpers";
import { dialogBoxCreate } from "./DialogBox"; import { dialogBoxCreate } from "./DialogBox";
let layerCounter = 0; let layerCounter = 0;
@ -222,7 +221,7 @@ function LogWindow(props: IProps): React.ReactElement {
if (server === null) return; if (server === null) return;
const s = findRunningScript(script.filename, script.args, server); const s = findRunningScript(script.filename, script.args, server);
if (s === null) { if (s === null) {
const baseScript = server.scripts.find((serverScript) => areFilesEqual(serverScript.filename, script.filename)); const baseScript = server.scripts.get(script.filename);
if (!baseScript) { if (!baseScript) {
return dialogBoxCreate( return dialogBoxCreate(
`Could not launch script. The script ${script.filename} no longer exists on the server ${server.hostname}.`, `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"; import { getRandomByte } from "./helpers/getRandomByte";
/** /**
* Generate a random IP address * Generate a random IP address
* Does not check to see if the IP already exists in the game * Does not check to see if the IP already exists in the game
*/ */
export const createRandomIp = (): string => export const createRandomIp = (): IPAddress =>
`${getRandomByte(99)}.${getRandomByte(9)}.${getRandomByte(9)}.${getRandomByte(9)}`; `${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 */ /* Generic Reviver, toJSON, and fromJSON functions used for saving and loading objects */
import { ObjectValidator, validateObject } from "./Validator"; 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 { export interface IReviverValue {
ctor: string; ctor: string;
@ -38,15 +43,7 @@ export function Reviver(_key: string, value: unknown): any {
return obj; return obj;
} }
export const constructorsForReviver: Partial< export const constructorsForReviver: Partial<Record<string, JsonableClass>> = { JSONSet, JSONMap };
Record<
string,
(new () => object) & {
fromJSON: (value: IReviverValue) => any;
validationData?: ObjectValidator<any>;
}
>
> = {};
/** /**
* A generic "toJSON" function that creates the data expected by Reviver. * 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 server of GetAllServers()) {
for (const change of detect) { for (const change of detect) {
const s: IFileLine[] = []; 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"); const lines = script.code.split("\n");
for (let i = 0; i < lines.length; i++) { for (let i = 0; i < lines.length; i++) {
if (lines[i].includes(change[0])) { if (lines[i].includes(change[0])) {
@ -121,7 +125,8 @@ export function v1APIBreak(): void {
home.writeToTextFile("v1_DETECTED_CHANGES.txt", txt); 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[] = []; const backups: Script[] = [];
for (const script of server.scripts) { for (const script of server.scripts) {
if (!hasChanges(script.code)) continue; 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); processScript(rules, script);
} }

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

@ -5,6 +5,7 @@ import { RunningScript } from "../../../src/Script/RunningScript";
import { AddToAllServers, DeleteServer } from "../../../src/Server/AllServers"; import { AddToAllServers, DeleteServer } from "../../../src/Server/AllServers";
import { WorkerScriptStartStopEventEmitter } from "../../../src/Netscript/WorkerScriptStartStopEventEmitter"; import { WorkerScriptStartStopEventEmitter } from "../../../src/Netscript/WorkerScriptStartStopEventEmitter";
import { AlertEvents } from "../../../src/ui/React/AlertManager"; 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 // Replace Blob/ObjectURL functions, because they don't work natively in Jest
global.Blob = class extends Blob { global.Blob = class extends Blob {
@ -89,10 +90,11 @@ test.each([
expect(server.writeToScriptFile(s.name, s.code)).toEqual({ success: true, overwritten: false }); 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); expect(script.filename).toEqual(scripts[scripts.length - 1].name);
const ramUsage = script.getRamUsage(server.scripts); const ramUsage = script.getRamUsage(server.scripts);
if (!ramUsage) throw new Error(`ramUsage calculated to be ${ramUsage}`);
const runningScript = new RunningScript(script, ramUsage as number); const runningScript = new RunningScript(script, ramUsage as number);
expect(startWorkerScript(runningScript, server)).toBeGreaterThan(0); expect(startWorkerScript(runningScript, server)).toBeGreaterThan(0);
// We don't care about start, so subscribe after that. Await script death. // 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 = ` const code = `
export async function main(ns) { } export async function main(ns) { }
`; `;
const calculated = calculateRamUsage(code, []).cost; const calculated = calculateRamUsage(code, new Map()).cost;
expectCost(calculated, 0); expectCost(calculated, 0);
}); });
@ -31,7 +31,7 @@ describe("Parsing NetScript code to work out static RAM costs", function () {
ns.print("Slum snakes r00l!"); ns.print("Slum snakes r00l!");
} }
`; `;
const calculated = calculateRamUsage(code, []).cost; const calculated = calculateRamUsage(code, new Map()).cost;
expectCost(calculated, 0); expectCost(calculated, 0);
}); });
@ -41,7 +41,7 @@ describe("Parsing NetScript code to work out static RAM costs", function () {
await ns.hack("joesguns"); await ns.hack("joesguns");
} }
`; `;
const calculated = calculateRamUsage(code, []).cost; const calculated = calculateRamUsage(code, new Map()).cost;
expectCost(calculated, HackCost); expectCost(calculated, HackCost);
}); });
@ -51,7 +51,7 @@ describe("Parsing NetScript code to work out static RAM costs", function () {
await X.hack("joesguns"); await X.hack("joesguns");
} }
`; `;
const calculated = calculateRamUsage(code, []).cost; const calculated = calculateRamUsage(code, new Map()).cost;
expectCost(calculated, HackCost); expectCost(calculated, HackCost);
}); });
@ -62,7 +62,7 @@ describe("Parsing NetScript code to work out static RAM costs", function () {
await ns.hack("joesguns"); await ns.hack("joesguns");
} }
`; `;
const calculated = calculateRamUsage(code, []).cost; const calculated = calculateRamUsage(code, new Map()).cost;
expectCost(calculated, HackCost); expectCost(calculated, HackCost);
}); });
@ -73,7 +73,7 @@ describe("Parsing NetScript code to work out static RAM costs", function () {
await ns.grow("joesguns"); await ns.grow("joesguns");
} }
`; `;
const calculated = calculateRamUsage(code, []).cost; const calculated = calculateRamUsage(code, new Map()).cost;
expectCost(calculated, HackCost + GrowCost); expectCost(calculated, HackCost + GrowCost);
}); });
@ -86,7 +86,7 @@ describe("Parsing NetScript code to work out static RAM costs", function () {
await ns.hack("joesguns"); await ns.hack("joesguns");
} }
`; `;
const calculated = calculateRamUsage(code, []).cost; const calculated = calculateRamUsage(code, new Map()).cost;
expectCost(calculated, HackCost); 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"); } async doHacking() { await this.ns.hack("joesguns"); }
} }
`; `;
const calculated = calculateRamUsage(code, []).cost; const calculated = calculateRamUsage(code, new Map()).cost;
expectCost(calculated, HackCost); 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"); } async doHacking() { await this.#ns.hack("joesguns"); }
} }
`; `;
const calculated = calculateRamUsage(code, []).cost; const calculated = calculateRamUsage(code, new Map()).cost;
expectCost(calculated, HackCost); expectCost(calculated, HackCost);
}); });
}); });
@ -129,7 +129,7 @@ describe("Parsing NetScript code to work out static RAM costs", function () {
} }
function get() { return 0; } function get() { return 0; }
`; `;
const calculated = calculateRamUsage(code, []).cost; const calculated = calculateRamUsage(code, new Map()).cost;
expectCost(calculated, 0); expectCost(calculated, 0);
}); });
@ -140,7 +140,7 @@ describe("Parsing NetScript code to work out static RAM costs", function () {
} }
function purchaseNode() { return 0; } 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 // Works at present, because the parser checks the namespace only, not the function name
expectCost(calculated, 0); expectCost(calculated, 0);
}); });
@ -153,7 +153,7 @@ describe("Parsing NetScript code to work out static RAM costs", function () {
} }
function getTask() { return 0; } function getTask() { return 0; }
`; `;
const calculated = calculateRamUsage(code, []).cost; const calculated = calculateRamUsage(code, new Map()).cost;
expectCost(calculated, 0); expectCost(calculated, 0);
}); });
}); });
@ -165,7 +165,7 @@ describe("Parsing NetScript code to work out static RAM costs", function () {
ns.hacknet.purchaseNode(0); ns.hacknet.purchaseNode(0);
} }
`; `;
const calculated = calculateRamUsage(code, []).cost; const calculated = calculateRamUsage(code, new Map()).cost;
expectCost(calculated, HacknetCost); expectCost(calculated, HacknetCost);
}); });
@ -175,7 +175,7 @@ describe("Parsing NetScript code to work out static RAM costs", function () {
ns.corporation.getCorporation(); ns.corporation.getCorporation();
} }
`; `;
const calculated = calculateRamUsage(code, []).cost; const calculated = calculateRamUsage(code, new Map()).cost;
expectCost(calculated, MaxCost); expectCost(calculated, MaxCost);
}); });
@ -186,7 +186,7 @@ describe("Parsing NetScript code to work out static RAM costs", function () {
ns.corporation.getCorporation(); ns.corporation.getCorporation();
} }
`; `;
const calculated = calculateRamUsage(code, []).cost; const calculated = calculateRamUsage(code, new Map()).cost;
expectCost(calculated, MaxCost); expectCost(calculated, MaxCost);
}); });
@ -196,7 +196,7 @@ describe("Parsing NetScript code to work out static RAM costs", function () {
ns.sleeve.getTask(3); ns.sleeve.getTask(3);
} }
`; `;
const calculated = calculateRamUsage(code, []).cost; const calculated = calculateRamUsage(code, new Map()).cost;
expectCost(calculated, SleeveGetTaskCost); expectCost(calculated, SleeveGetTaskCost);
}); });
}); });
@ -214,7 +214,7 @@ describe("Parsing NetScript code to work out static RAM costs", function () {
dummy(); dummy();
} }
`; `;
const calculated = calculateRamUsage(code, [lib]).cost; const calculated = calculateRamUsage(code, new Map([["libTest.js", lib]])).cost;
expectCost(calculated, 0); expectCost(calculated, 0);
}); });
@ -230,7 +230,7 @@ describe("Parsing NetScript code to work out static RAM costs", function () {
await doHack(ns); await doHack(ns);
} }
`; `;
const calculated = calculateRamUsage(code, [lib]).cost; const calculated = calculateRamUsage(code, new Map([["libTest.js", lib]])).cost;
expectCost(calculated, HackCost); expectCost(calculated, HackCost);
}); });
@ -247,7 +247,7 @@ describe("Parsing NetScript code to work out static RAM costs", function () {
await doHack(ns); await doHack(ns);
} }
`; `;
const calculated = calculateRamUsage(code, [lib]).cost; const calculated = calculateRamUsage(code, new Map([["libTest.js", lib]])).cost;
expectCost(calculated, HackCost); expectCost(calculated, HackCost);
}); });
@ -264,7 +264,7 @@ describe("Parsing NetScript code to work out static RAM costs", function () {
await test.doHack(ns); await test.doHack(ns);
} }
`; `;
const calculated = calculateRamUsage(code, [lib]).cost; const calculated = calculateRamUsage(code, new Map([["libTest.js", lib]])).cost;
expectCost(calculated, HackCost + GrowCost); expectCost(calculated, HackCost + GrowCost);
}); });
@ -286,7 +286,7 @@ describe("Parsing NetScript code to work out static RAM costs", function () {
await test.doHack(ns); await test.doHack(ns);
} }
`; `;
const calculated = calculateRamUsage(code, [lib]).cost; const calculated = calculateRamUsage(code, new Map([["libTest.js", lib]])).cost;
expectCost(calculated, HackCost); expectCost(calculated, HackCost);
}); });
@ -312,7 +312,7 @@ describe("Parsing NetScript code to work out static RAM costs", function () {
await growerInstance.doGrow(); await growerInstance.doGrow();
} }
`; `;
const calculated = calculateRamUsage(code, [lib]).cost; const calculated = calculateRamUsage(code, new Map([["libTest.js", lib]])).cost;
expectCost(calculated, GrowCost); expectCost(calculated, GrowCost);
}); });
}); });

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