mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2024-12-20 05:05:47 +01:00
CODEBASE: Add Jsonable Map and Set types, move player.sourceFiles to a map (#473)
This commit is contained in:
parent
c44bdc1018
commit
0df984eea0
@ -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,34 +122,22 @@ 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(
|
console.error(
|
||||||
"Failed to find underlying Script object in WorkerScript.getScript(). This probably means somethings wrong",
|
"Failed to find underlying Script object in WorkerScript.getScript(). This probably means somethings wrong",
|
||||||
);
|
);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
return script;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the script with the specified filename on the specified server,
|
* Returns the script with the specified filename on the specified server,
|
||||||
* 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,37 +1362,35 @@ 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;
|
return;
|
||||||
}
|
}
|
||||||
mode === "w" ? (script.code = String(data)) : (script.code += 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
|
// Set ram to null so a recalc is performed the next time ram usage is needed
|
||||||
script.invalidateModule();
|
script.invalidateModule();
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
// Write to text file
|
// Write to text file
|
||||||
if (!fn.endsWith(".txt")) throw helpers.makeRuntimeErrorMsg(ctx, `Invalid filename: ${fn}`);
|
if (!filename.endsWith(".txt")) throw helpers.makeRuntimeErrorMsg(ctx, `Invalid filename: ${filename}`);
|
||||||
const txtFile = getTextFile(fn, server);
|
const txtFile = getTextFile(filename, server);
|
||||||
if (txtFile == null) {
|
if (txtFile == null) {
|
||||||
createTextFile(fn, String(data), server);
|
createTextFile(filename, String(data), server);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (mode === "w") {
|
if (mode === "w") {
|
||||||
@ -1515,13 +1509,11 @@ export const ns: InternalAPI<NSFull> = {
|
|||||||
getScriptName: (ctx) => () => {
|
getScriptName: (ctx) => () => {
|
||||||
return ctx.workerScript.name;
|
return ctx.workerScript.name;
|
||||||
},
|
},
|
||||||
getScriptRam:
|
getScriptRam: (ctx) => (_scriptname, _hostname) => {
|
||||||
(ctx) =>
|
|
||||||
(_scriptname, _hostname = ctx.workerScript.hostname) => {
|
|
||||||
const scriptname = helpers.string(ctx, "scriptname", _scriptname);
|
const scriptname = helpers.string(ctx, "scriptname", _scriptname);
|
||||||
const hostname = helpers.string(ctx, "hostname", _hostname);
|
const hostname = helpers.string(ctx, "hostname", _hostname ?? ctx.workerScript.hostname);
|
||||||
const server = helpers.getServer(ctx, hostname);
|
const server = helpers.getServer(ctx, hostname);
|
||||||
const script = server.scripts.find((serverScript) => areFilesEqual(serverScript.filename, scriptname));
|
const script = server.scripts.get(scriptname);
|
||||||
if (!script) return 0;
|
if (!script) return 0;
|
||||||
const ramUsage = script.getRamUsage(server.scripts);
|
const ramUsage = script.getRamUsage(server.scripts);
|
||||||
if (!ramUsage) {
|
if (!ramUsage) {
|
||||||
@ -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.code = code;
|
|
||||||
// Set ramUsage to null in order to force recalculation on next run
|
|
||||||
script.invalidateModule();
|
script.invalidateModule();
|
||||||
ret.overwritten = true;
|
script.code = code;
|
||||||
ret.success = true;
|
return { success: true, overwritten: 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,13 +43,9 @@ 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);
|
const singleRamUsage = script.getRamUsage(server.scripts);
|
||||||
if (!singleRamUsage) return Terminal.error("Error while calculating ram usage for this script.");
|
if (!singleRamUsage) return Terminal.error("Error while calculating ram usage for this script.");
|
||||||
const ramUsage = singleRamUsage * numThreads;
|
const ramUsage = singleRamUsage * numThreads;
|
||||||
@ -86,7 +82,4 @@ export function runScript(commandArgs: (string | number | boolean)[], server: Ba
|
|||||||
LogBoxEvents.emit(runningScript);
|
LogBoxEvents.emit(runningScript);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
Terminal.error("No such script");
|
|
||||||
}
|
}
|
||||||
|
@ -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
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
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,7 +255,6 @@ 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);
|
||||||
@ -270,7 +269,6 @@ const Engine: {
|
|||||||
for (let i = 0; i < numContracts; i++) {
|
for (let i = 0; i < numContracts; i++) {
|
||||||
generateRandomContract();
|
generateRandomContract();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
let offlineReputation = 0;
|
let offlineReputation = 0;
|
||||||
const offlineHackingIncome = (Player.moneySourceA.hacking / Player.playtimeSinceLastAug) * timeOffline * 0.75;
|
const offlineHackingIncome = (Player.moneySourceA.hacking / Player.playtimeSinceLastAug) * timeOffline * 0.75;
|
||||||
|
@ -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"
|
||||||
],
|
],
|
||||||
|
Loading…
Reference in New Issue
Block a user