FILES: Path rework & typesafety (#479)

* Added new types for various file paths, all in the Paths folder.
* TypeSafety and other helper functions related to these types
* Added basic globbing support with * and ?. Currently only implemented for Script/Text, on nano and download terminal commands
* Enforcing the new types throughout the codebase, plus whatever rewrites happened along the way
* Server.textFiles is now a map
* TextFile no longer uses a fn property, now it is filename
* Added a shared ContentFile interface for shared functionality between TextFile and Script.
* related to ContentFile change above, the player is now allowed to move a text file to a script file and vice versa.
* File paths no longer conditionally start with slashes, and all directory names other than root have ending slashes. The player is still able to provide paths starting with / but this now indicates that the player is specifying an absolute path instead of one relative to root.
* Singularized the MessageFilename and LiteratureName enums
* Because they now only accept correct types, server.writeToXFile functions now always succeed (the only reasons they could fail before were invalid filepath).
* Fix several issues with tab completion, which included pretty much a complete rewrite
* Changed the autocomplete display options so there's less chance it clips outside the display area.
* Turned CompletedProgramName into an enum.
* Got rid of programsMetadata, and programs and DarkWebItems are now initialized immediately instead of relying on initializers called from the engine.
* For any executable (program, cct, or script file) pathing can be used directly to execute without using the run command (previously the command had to start with ./ and it wasn't actually using pathing).
This commit is contained in:
Snarling
2023-04-24 10:26:57 -04:00
committed by GitHub
parent 6f56f35943
commit e0272ad4af
93 changed files with 3293 additions and 4297 deletions
+58 -54
View File
@@ -6,7 +6,6 @@ import { StaticAugmentations } from "../Augmentation/StaticAugmentations";
import { augmentationExists, installAugmentations } from "../Augmentation/AugmentationHelpers";
import { AugmentationNames } from "../Augmentation/data/AugmentationNames";
import { CONSTANTS } from "../Constants";
import { isString } from "../utils/helpers/isString";
import { RunningScript } from "../Script/RunningScript";
import { calculateAchievements } from "../Achievements/Achievements";
@@ -52,6 +51,8 @@ import { calculateCrimeWorkStats } from "../Work/Formulas";
import { findEnumMember } from "../utils/helpers/enum";
import { Engine } from "../engine";
import { checkEnum } from "../utils/helpers/enum";
import { ScriptFilePath, resolveScriptFilePath } from "../Paths/ScriptFilePath";
import { root } from "../Paths/Directory";
export function NetscriptSingularity(): InternalAPI<ISingularity> {
const getAugmentation = function (ctx: NetscriptContext, name: string): Augmentation {
@@ -76,7 +77,7 @@ export function NetscriptSingularity(): InternalAPI<ISingularity> {
return company;
};
const runAfterReset = function (cbScript: string | null = null) {
const runAfterReset = function (cbScript: ScriptFilePath) {
//Run a script after reset
if (!cbScript) return;
const home = Player.getHomeComputer();
@@ -87,7 +88,9 @@ export function NetscriptSingularity(): InternalAPI<ISingularity> {
return Terminal.error(`Attempted to launch ${cbScript} after reset but could not calculate ram usage.`);
}
const ramAvailable = home.maxRam - home.ramUsed;
if (ramUsage > ramAvailable + 0.001) return;
if (ramUsage > ramAvailable + 0.001) {
return Terminal.error(`Attempted to launch ${cbScript} after reset but there was not enough ram.`);
}
// Start script with no args and 1 thread (default).
const runningScriptObj = new RunningScript(script, ramUsage, []);
startWorkerScript(runningScriptObj, home);
@@ -190,7 +193,7 @@ export function NetscriptSingularity(): InternalAPI<ISingularity> {
const res = purchaseAugmentation(aug, fac, true);
helpers.log(ctx, () => res);
if (isString(res) && res.startsWith("You purchased")) {
if (res.startsWith("You purchased")) {
Player.gainIntelligenceExp(CONSTANTS.IntelligenceSingFnBaseExpGain * 10);
return true;
} else {
@@ -199,7 +202,10 @@ export function NetscriptSingularity(): InternalAPI<ISingularity> {
},
softReset: (ctx) => (_cbScript) => {
helpers.checkSingularityAccess(ctx);
const cbScript = _cbScript ? helpers.string(ctx, "cbScript", _cbScript) : "";
const cbScript = _cbScript
? resolveScriptFilePath(helpers.string(ctx, "cbScript", _cbScript), ctx.workerScript.name)
: false;
if (cbScript === null) throw helpers.makeRuntimeErrorMsg(ctx, `Could not resolve file path: ${_cbScript}`);
helpers.log(ctx, () => "Soft resetting. This will cause this script to be killed");
installAugmentations(true);
@@ -207,7 +213,10 @@ export function NetscriptSingularity(): InternalAPI<ISingularity> {
},
installAugmentations: (ctx) => (_cbScript) => {
helpers.checkSingularityAccess(ctx);
const cbScript = _cbScript ? helpers.string(ctx, "cbScript", _cbScript) : "";
const cbScript = _cbScript
? resolveScriptFilePath(helpers.string(ctx, "cbScript", _cbScript), ctx.workerScript.name)
: false;
if (cbScript === null) throw helpers.makeRuntimeErrorMsg(ctx, `Could not resolve file path: ${_cbScript}`);
if (Player.queuedAugmentations.length === 0) {
helpers.log(ctx, () => "You do not have any Augmentations to be installed.");
@@ -502,7 +511,7 @@ export function NetscriptSingularity(): InternalAPI<ISingularity> {
Player.getCurrentServer().isConnectedTo = false;
Player.currentServer = Player.getHomeComputer().hostname;
Player.getCurrentServer().isConnectedTo = true;
Terminal.setcwd("/");
Terminal.setcwd(root);
return true;
}
@@ -515,7 +524,7 @@ export function NetscriptSingularity(): InternalAPI<ISingularity> {
Player.getCurrentServer().isConnectedTo = false;
Player.currentServer = target.hostname;
Player.getCurrentServer().isConnectedTo = true;
Terminal.setcwd("/");
Terminal.setcwd(root);
return true;
}
}
@@ -526,7 +535,7 @@ export function NetscriptSingularity(): InternalAPI<ISingularity> {
Player.getCurrentServer().isConnectedTo = false;
Player.currentServer = target.hostname;
Player.getCurrentServer().isConnectedTo = true;
Terminal.setcwd("/");
Terminal.setcwd(root);
return true;
}
@@ -1189,54 +1198,49 @@ export function NetscriptSingularity(): InternalAPI<ISingularity> {
}
return item.price;
},
b1tflum3:
(ctx) =>
(_nextBN, _callbackScript = "") => {
helpers.checkSingularityAccess(ctx);
const nextBN = helpers.number(ctx, "nextBN", _nextBN);
const callbackScript = helpers.string(ctx, "callbackScript", _callbackScript);
helpers.checkSingularityAccess(ctx);
enterBitNode(true, Player.bitNodeN, nextBN);
if (callbackScript)
setTimeout(() => {
runAfterReset(callbackScript);
}, 0);
},
destroyW0r1dD43m0n:
(ctx) =>
(_nextBN, _callbackScript = "") => {
helpers.checkSingularityAccess(ctx);
const nextBN = helpers.number(ctx, "nextBN", _nextBN);
if (nextBN > 13 || nextBN < 1 || !Number.isInteger(nextBN)) {
throw new Error(`Invalid bitnode specified: ${_nextBN}`);
}
const callbackScript = helpers.string(ctx, "callbackScript", _callbackScript);
b1tflum3: (ctx) => (_nextBN, _cbScript) => {
helpers.checkSingularityAccess(ctx);
const nextBN = helpers.number(ctx, "nextBN", _nextBN);
const cbScript = _cbScript
? resolveScriptFilePath(helpers.string(ctx, "cbScript", _cbScript), ctx.workerScript.name)
: false;
if (cbScript === null) throw helpers.makeRuntimeErrorMsg(ctx, `Could not resolve file path: ${_cbScript}`);
enterBitNode(true, Player.bitNodeN, nextBN);
if (cbScript) setTimeout(() => runAfterReset(cbScript), 0);
},
destroyW0r1dD43m0n: (ctx) => (_nextBN, _cbScript) => {
helpers.checkSingularityAccess(ctx);
const nextBN = helpers.number(ctx, "nextBN", _nextBN);
if (nextBN > 13 || nextBN < 1 || !Number.isInteger(nextBN)) {
throw new Error(`Invalid bitnode specified: ${_nextBN}`);
}
const cbScript = _cbScript
? resolveScriptFilePath(helpers.string(ctx, "cbScript", _cbScript), ctx.workerScript.name)
: false;
if (cbScript === null) throw helpers.makeRuntimeErrorMsg(ctx, `Could not resolve file path: ${_cbScript}`);
const wd = GetServer(SpecialServers.WorldDaemon);
if (!(wd instanceof Server)) throw new Error("WorldDaemon was not a normal server. This is a bug contact dev.");
const hackingRequirements = () => {
if (Player.skills.hacking < wd.requiredHackingSkill) return false;
if (!wd.hasAdminRights) return false;
return true;
};
const bladeburnerRequirements = () => {
if (!Player.bladeburner) return false;
return Player.bladeburner.blackops[BlackOperationNames.OperationDaedalus];
};
const wd = GetServer(SpecialServers.WorldDaemon);
if (!(wd instanceof Server)) throw new Error("WorldDaemon was not a normal server. This is a bug contact dev.");
const hackingRequirements = () => {
if (Player.skills.hacking < wd.requiredHackingSkill) return false;
if (!wd.hasAdminRights) return false;
return true;
};
const bladeburnerRequirements = () => {
if (!Player.bladeburner) return false;
return Player.bladeburner.blackops[BlackOperationNames.OperationDaedalus];
};
if (!hackingRequirements() && !bladeburnerRequirements()) {
helpers.log(ctx, () => "Requirements not met to destroy the world daemon");
return;
}
if (!hackingRequirements() && !bladeburnerRequirements()) {
helpers.log(ctx, () => "Requirements not met to destroy the world daemon");
return;
}
wd.backdoorInstalled = true;
calculateAchievements();
enterBitNode(false, Player.bitNodeN, nextBN);
if (callbackScript)
setTimeout(() => {
runAfterReset(callbackScript);
}, 0);
},
wd.backdoorInstalled = true;
calculateAchievements();
enterBitNode(false, Player.bitNodeN, nextBN);
if (cbScript) setTimeout(() => runAfterReset(cbScript), 0);
},
getCurrentWork: () => () => {
if (!Player.currentWork) return null;
return Player.currentWork.APICopy();