bitburner-src/src/Terminal/Terminal.ts
Sage Pointer 0bd8d3cb8f Fix some achievements not triggered if hacked with backdoor command
Moving to BitVerse and returning from function happened before setting backdoorInstalled property to true, so Achievement Handler believed the current BitNode was not finished (unless we applied backdoor through hack command, which has correct code).
2022-01-05 22:23:10 +02:00

815 lines
28 KiB
TypeScript

import { ITerminal, Output, Link, RawOutput, TTimer } from "./ITerminal";
import { IRouter } from "../ui/Router";
import { IPlayer } from "../PersonObjects/IPlayer";
import { HacknetServer } from "../Hacknet/HacknetServer";
import { BaseServer } from "../Server/BaseServer";
import { Server } from "../Server/Server";
import { Programs } from "../Programs/Programs";
import { CodingContractResult } from "../CodingContracts";
import { TerminalEvents, TerminalClearEvents } from "./TerminalEvents";
import { TextFile } from "../TextFile";
import { Script } from "../Script/Script";
import { isScriptFilename } from "../Script/isScriptFilename";
import { CONSTANTS } from "../Constants";
import { GetServer, GetAllServers } from "../Server/AllServers";
import { removeLeadingSlash, isInRootDirectory, evaluateFilePath } from "./DirectoryHelpers";
import { checkIfConnectedToDarkweb } from "../DarkWeb/DarkWeb";
import { iTutorialNextStep, iTutorialSteps, ITutorial } from "../InteractiveTutorial";
import { getServerOnNetwork, processSingleServerGrowth } from "../Server/ServerHelpers";
import { ParseCommand, ParseCommands } from "./Parser";
import { SpecialServers } from "../Server/data/SpecialServers";
import { Settings } from "../Settings/Settings";
import { createProgressBarText } from "../utils/helpers/createProgressBarText";
import {
calculateHackingChance,
calculateHackingExpGain,
calculatePercentMoneyHacked,
calculateHackingTime,
calculateGrowTime,
calculateWeakenTime,
} from "../Hacking";
import { numeralWrapper } from "../ui/numeralFormat";
import { convertTimeMsToTimeElapsedString } from "../utils/StringHelperFunctions";
import { alias } from "./commands/alias";
import { analyze } from "./commands/analyze";
import { backdoor } from "./commands/backdoor";
import { buy } from "./commands/buy";
import { cat } from "./commands/cat";
import { cd } from "./commands/cd";
import { check } from "./commands/check";
import { connect } from "./commands/connect";
import { cp } from "./commands/cp";
import { download } from "./commands/download";
import { expr } from "./commands/expr";
import { free } from "./commands/free";
import { grow } from "./commands/grow";
import { hack } from "./commands/hack";
import { help } from "./commands/help";
import { home } from "./commands/home";
import { hostname } from "./commands/hostname";
import { kill } from "./commands/kill";
import { killall } from "./commands/killall";
import { ls } from "./commands/ls";
import { lscpu } from "./commands/lscpu";
import { mem } from "./commands/mem";
import { mv } from "./commands/mv";
import { nano } from "./commands/nano";
import { ps } from "./commands/ps";
import { rm } from "./commands/rm";
import { run } from "./commands/run";
import { scan } from "./commands/scan";
import { scananalyze } from "./commands/scananalyze";
import { scp } from "./commands/scp";
import { sudov } from "./commands/sudov";
import { tail } from "./commands/tail";
import { top } from "./commands/top";
import { unalias } from "./commands/unalias";
import { vim } from "./commands/vim";
import { weaken } from "./commands/weaken";
import { wget } from "./commands/wget";
import { hash } from "../hash/hash";
export class Terminal implements ITerminal {
// Flags to determine whether the player is currently running a hack or an analyze
action: TTimer | null = null;
commandHistory: string[] = [];
commandHistoryIndex = 0;
outputHistory: (Output | Link | RawOutput)[] = [
new Output(`Bitburner v${CONSTANTS.VersionString} (${hash()})`, "primary"),
];
// True if a Coding Contract prompt is opened
contractOpen = false;
// Full Path of current directory
// Excludes the trailing forward slash
currDir = "/";
process(router: IRouter, player: IPlayer, cycles: number): void {
if (this.action === null) return;
this.action.timeLeft -= (CONSTANTS._idleSpeed * cycles) / 1000;
if (this.action.timeLeft < 0.01) this.finishAction(router, player, false);
TerminalEvents.emit();
}
append(item: Output | Link | RawOutput): void {
this.outputHistory.push(item);
if (this.outputHistory.length > Settings.MaxTerminalCapacity) {
this.outputHistory.splice(0, this.outputHistory.length - Settings.MaxTerminalCapacity);
}
TerminalEvents.emit();
}
print(s: string): void {
this.append(new Output(s, "primary"));
}
printRaw(node: React.ReactNode): void {
this.append(new RawOutput(node));
}
error(s: string): void {
this.append(new Output(s, "error"));
}
success(s: string): void {
this.append(new Output(s, "success"));
}
info(s: string): void {
this.append(new Output(s, "info"));
}
warn(s: string): void {
this.append(new Output(s, "warn"));
}
startHack(player: IPlayer): void {
// Hacking through Terminal should be faster than hacking through a script
const server = player.getCurrentServer();
if (server instanceof HacknetServer) {
this.error("Cannot hack this kind of server");
return;
}
if (!(server instanceof Server)) throw new Error("server should be normal server");
this.startAction(calculateHackingTime(server, player) / 4, "h");
}
startGrow(player: IPlayer): void {
const server = player.getCurrentServer();
if (server instanceof HacknetServer) {
this.error("Cannot hack this kind of server");
return;
}
if (!(server instanceof Server)) throw new Error("server should be normal server");
this.startAction(calculateGrowTime(server, player) / 16, "g");
}
startWeaken(player: IPlayer): void {
const server = player.getCurrentServer();
if (server instanceof HacknetServer) {
this.error("Cannot hack this kind of server");
return;
}
if (!(server instanceof Server)) throw new Error("server should be normal server");
this.startAction(calculateWeakenTime(server, player) / 16, "w");
}
startBackdoor(player: IPlayer): void {
// Backdoor should take the same amount of time as hack
const server = player.getCurrentServer();
if (server instanceof HacknetServer) {
this.error("Cannot backdoor this kind of server");
return;
}
if (!(server instanceof Server)) throw new Error("server should be normal server");
this.startAction(calculateHackingTime(server, player) / 4, "b");
}
startAnalyze(): void {
this.print("Analyzing system...");
this.startAction(1, "a");
}
startAction(n: number, action: "h" | "b" | "a" | "g" | "w"): void {
this.action = new TTimer(n, action);
}
// Complete the hack/analyze command
finishHack(router: IRouter, player: IPlayer, cancelled = false): void {
if (cancelled) return;
const server = player.getCurrentServer();
if (server instanceof HacknetServer) {
this.error("Cannot hack this kind of server");
return;
}
if (!(server instanceof Server)) throw new Error("server should be normal server");
// Calculate whether hack was successful
const hackChance = calculateHackingChance(server, player);
const rand = Math.random();
const expGainedOnSuccess = calculateHackingExpGain(server, player);
const expGainedOnFailure = expGainedOnSuccess / 4;
if (rand < hackChance) {
// Success!
server.backdoorInstalled = true;
if (SpecialServers.WorldDaemon === server.hostname) {
router.toBitVerse(false, false);
return;
}
let moneyGained = calculatePercentMoneyHacked(server, player);
moneyGained = Math.floor(server.moneyAvailable * moneyGained);
if (moneyGained <= 0) {
moneyGained = 0;
} // Safety check
server.moneyAvailable -= moneyGained;
player.gainMoney(moneyGained, "hacking");
player.gainHackingExp(expGainedOnSuccess);
player.gainIntelligenceExp(expGainedOnSuccess / CONSTANTS.IntelligenceTerminalHackBaseExpGain);
const oldSec = server.hackDifficulty;
server.fortify(CONSTANTS.ServerFortifyAmount);
const newSec = server.hackDifficulty;
this.print(
`Hack successful! Gained ${numeralWrapper.formatMoney(moneyGained)} and ${numeralWrapper.formatExp(
expGainedOnSuccess,
)} hacking exp`,
);
this.print(
`Security increased from ${numeralWrapper.formatSecurity(oldSec)} to ${numeralWrapper.formatSecurity(newSec)}`,
);
} else {
// Failure
player.gainHackingExp(expGainedOnFailure);
this.print(
`Failed to hack ${server.hostname}. Gained ${numeralWrapper.formatExp(expGainedOnFailure)} hacking exp`,
);
}
}
finishGrow(player: IPlayer, cancelled = false): void {
if (cancelled) return;
const server = player.getCurrentServer();
if (server instanceof HacknetServer) {
this.error("Cannot hack this kind of server");
return;
}
if (!(server instanceof Server)) throw new Error("server should be normal server");
const expGain = calculateHackingExpGain(server, player);
const oldSec = server.hackDifficulty;
const growth = processSingleServerGrowth(server, 25, player, server.cpuCores) - 1;
const newSec = server.hackDifficulty;
this.print(
`Available money on '${server.hostname}' grown by ${numeralWrapper.formatPercentage(
growth,
6,
)}. Gained ${numeralWrapper.formatExp(expGain)} hacking exp.`,
);
this.print(
`Security increased from ${numeralWrapper.formatSecurity(oldSec)} to ${numeralWrapper.formatSecurity(newSec)}`,
);
}
finishWeaken(player: IPlayer, cancelled = false): void {
if (cancelled) return;
const server = player.getCurrentServer();
if (server instanceof HacknetServer) {
this.error("Cannot hack this kind of server");
return;
}
if (!(server instanceof Server)) throw new Error("server should be normal server");
const expGain = calculateHackingExpGain(server, player);
const oldSec = server.hackDifficulty;
server.weaken(CONSTANTS.ServerWeakenAmount);
const newSec = server.hackDifficulty;
this.print(
`Security decreased from ${numeralWrapper.formatSecurity(oldSec)} to ${numeralWrapper.formatSecurity(
newSec,
)} (min: ${numeralWrapper.formatSecurity(server.minDifficulty)})` +
` and Gained ${numeralWrapper.formatExp(expGain)} hacking exp.`,
);
}
finishBackdoor(router: IRouter, player: IPlayer, cancelled = false): void {
if (!cancelled) {
const server = player.getCurrentServer();
if (server instanceof HacknetServer) {
this.error("Cannot hack this kind of server");
return;
}
if (!(server instanceof Server)) throw new Error("server should be normal server");
server.backdoorInstalled = true;
if (SpecialServers.WorldDaemon === server.hostname) {
if (player.bitNodeN == null) {
player.bitNodeN = 1;
}
router.toBitVerse(false, false);
return;
}
this.print("Backdoor successful!");
}
}
finishAnalyze(player: IPlayer, cancelled = false): void {
if (!cancelled) {
const currServ = player.getCurrentServer();
const isHacknet = currServ instanceof HacknetServer;
this.print(currServ.hostname + ": ");
const org = currServ.organizationName;
this.print("Organization name: " + (!isHacknet ? org : "player"));
const hasAdminRights = (!isHacknet && currServ.hasAdminRights) || isHacknet;
this.print("Root Access: " + (hasAdminRights ? "YES" : "NO"));
if (currServ instanceof Server) {
const hackingSkill = currServ.requiredHackingSkill;
this.print("Required hacking skill: " + (!isHacknet ? hackingSkill : "N/A"));
const security = currServ.hackDifficulty;
this.print("Server security level: " + (!isHacknet ? numeralWrapper.formatServerSecurity(security) : "N/A"));
const hackingChance = calculateHackingChance(currServ, player);
this.print("Chance to hack: " + (!isHacknet ? numeralWrapper.formatPercentage(hackingChance) : "N/A"));
const hackingTime = calculateHackingTime(currServ, player) * 1000;
this.print("Time to hack: " + (!isHacknet ? convertTimeMsToTimeElapsedString(hackingTime, true) : "N/A"));
}
this.print(
`Total money available on server: ${
currServ instanceof Server ? numeralWrapper.formatMoney(currServ.moneyAvailable) : "N/A"
}`,
);
if (currServ instanceof Server) {
const numPort = currServ.numOpenPortsRequired;
this.print("Required number of open ports for NUKE: " + (!isHacknet ? numPort : "N/A"));
this.print("SSH port: " + (currServ.sshPortOpen ? "Open" : "Closed"));
this.print("FTP port: " + (currServ.ftpPortOpen ? "Open" : "Closed"));
this.print("SMTP port: " + (currServ.smtpPortOpen ? "Open" : "Closed"));
this.print("HTTP port: " + (currServ.httpPortOpen ? "Open" : "Closed"));
this.print("SQL port: " + (currServ.sqlPortOpen ? "Open" : "Closed"));
}
}
}
finishAction(router: IRouter, player: IPlayer, cancelled = false): void {
if (this.action === null) {
if (!cancelled) throw new Error("Finish action called when there was no action");
return;
}
this.print(this.getProgressText());
if (this.action.action === "h") {
this.finishHack(router, player, cancelled);
} else if (this.action.action === "g") {
this.finishGrow(player, cancelled);
} else if (this.action.action === "w") {
this.finishWeaken(player, cancelled);
} else if (this.action.action === "b") {
this.finishBackdoor(router, player, cancelled);
} else if (this.action.action === "a") {
this.finishAnalyze(player, cancelled);
}
if (cancelled) {
this.print("Cancelled");
}
this.action = null;
TerminalEvents.emit();
}
getFile(player: IPlayer, filename: string): Script | TextFile | string | null {
if (isScriptFilename(filename)) {
return this.getScript(player, filename);
}
if (filename.endsWith(".lit")) {
return this.getLitFile(player, filename);
}
if (filename.endsWith(".txt")) {
return this.getTextFile(player, filename);
}
return null;
}
getFilepath(filename: string): string {
const path = evaluateFilePath(filename, this.cwd());
if (path == null) {
throw new Error(`Invalid file path specified: ${filename}`);
}
if (isInRootDirectory(path)) {
return removeLeadingSlash(path);
}
return path;
}
getScript(player: IPlayer, filename: string): Script | null {
const s = player.getCurrentServer();
const filepath = this.getFilepath(filename);
for (const script of s.scripts) {
if (filepath === script.filename) {
return script;
}
}
return null;
}
getTextFile(player: IPlayer, filename: string): TextFile | null {
const s = player.getCurrentServer();
const filepath = this.getFilepath(filename);
for (const txt of s.textFiles) {
if (filepath === txt.fn) {
return txt;
}
}
return null;
}
getLitFile(player: IPlayer, filename: string): string | null {
const s = player.getCurrentServer();
const filepath = this.getFilepath(filename);
for (const lit of s.messages) {
if (typeof lit === "string" && filepath === lit) {
return lit;
}
}
return null;
}
cwd(): string {
return this.currDir;
}
setcwd(dir: string): void {
this.currDir = dir;
TerminalEvents.emit();
}
async runContract(player: IPlayer, contractName: string): Promise<void> {
// There's already an opened contract
if (this.contractOpen) {
return this.error("There's already a Coding Contract in Progress");
}
const serv = player.getCurrentServer();
const contract = serv.getContract(contractName);
if (contract == null) {
return this.error("No such contract");
}
this.contractOpen = true;
const res = await contract.prompt();
switch (res) {
case CodingContractResult.Success:
if (contract.reward !== null) {
const reward = player.gainCodingContractReward(contract.reward, contract.getDifficulty());
this.print(`Contract SUCCESS - ${reward}`);
}
serv.removeContract(contract);
break;
case CodingContractResult.Failure:
++contract.tries;
if (contract.tries >= contract.getMaxNumTries()) {
this.error("Contract FAILED - Contract is now self-destructing");
serv.removeContract(contract);
} else {
this.error(`Contract FAILED - ${contract.getMaxNumTries() - contract.tries} tries remaining`);
}
break;
case CodingContractResult.Cancelled:
default:
this.print("Contract cancelled");
break;
}
this.contractOpen = false;
}
executeScanAnalyzeCommand(player: IPlayer, depth = 1, all = false): void {
// TODO Using array as stack for now, can make more efficient
this.print("~~~~~~~~~~ Beginning scan-analyze ~~~~~~~~~~");
this.print(" ");
// Map of all servers to keep track of which have been visited
const visited: {
[key: string]: number | undefined;
} = {};
for (const server of GetAllServers()) {
visited[server.hostname] = 0;
}
const stack: BaseServer[] = [];
const depthQueue: number[] = [0];
const currServ = player.getCurrentServer();
stack.push(currServ);
while (stack.length != 0) {
const s = stack.pop();
if (!s) continue;
const d = depthQueue.pop();
if (d === undefined) continue;
const isHacknet = s instanceof HacknetServer;
if (!all && (s as any).purchasedByPlayer && s.hostname != "home") {
continue; // Purchased server
} else if (visited[s.hostname] || d > depth) {
continue; // Already visited or out-of-depth
} else if (!all && isHacknet) {
continue; // Hacknet Server
} else {
visited[s.hostname] = 1;
}
for (let i = s.serversOnNetwork.length - 1; i >= 0; --i) {
const newS = getServerOnNetwork(s, i);
if (newS === null) continue;
stack.push(newS);
depthQueue.push(d + 1);
}
if (d == 0) {
continue;
} // Don't print current server
const titleDashes = Array((d - 1) * 4 + 1).join("-");
if (player.hasProgram(Programs.AutoLink.name)) {
this.append(new Link(titleDashes, s.hostname));
} else {
this.print(titleDashes + s.hostname);
}
const dashes = titleDashes + "--";
let c = "NO";
if (s.hasAdminRights) {
c = "YES";
}
this.print(
`${dashes}Root Access: ${c}${!isHacknet ? ", Required hacking skill: " + (s as any).requiredHackingSkill : ""}`,
);
if (s.hasOwnProperty("numOpenPortsRequired")) {
this.print(dashes + "Number of open ports required to NUKE: " + (s as any).numOpenPortsRequired);
}
this.print(dashes + "RAM: " + numeralWrapper.formatRAM(s.maxRam));
this.print(" ");
}
}
connectToServer(player: IPlayer, server: string): void {
const serv = GetServer(server);
if (serv == null) {
this.error("Invalid server. Connection failed.");
return;
}
player.getCurrentServer().isConnectedTo = false;
player.currentServer = serv.hostname;
player.getCurrentServer().isConnectedTo = true;
this.print("Connected to " + serv.hostname);
this.setcwd("/");
if (player.getCurrentServer().hostname == "darkweb") {
checkIfConnectedToDarkweb(); // Posts a 'help' message if connecting to dark web
}
}
executeCommands(router: IRouter, player: IPlayer, commands: string): void {
// Sanitize input
commands = commands.trim();
commands = commands.replace(/\s\s+/g, " "); // Replace all extra whitespace in command with a single space
// Handle Terminal History - multiple commands should be saved as one
if (this.commandHistory[this.commandHistory.length - 1] != commands) {
this.commandHistory.push(commands);
if (this.commandHistory.length > 50) {
this.commandHistory.splice(0, 1);
}
}
this.commandHistoryIndex = this.commandHistory.length;
const allCommands = ParseCommands(commands);
for (let i = 0; i < allCommands.length; i++) {
this.executeCommand(router, player, allCommands[i]);
}
}
clear(): void {
this.outputHistory = [new Output(`Bitburner v${CONSTANTS.VersionString} (${hash()})`, "primary")];
TerminalEvents.emit();
TerminalClearEvents.emit();
}
prestige(): void {
this.action = null;
this.clear();
}
executeCommand(router: IRouter, player: IPlayer, command: string): void {
if (this.action !== null) {
this.error(`Cannot execute command (${command}) while an action is in progress`);
return;
}
// Allow usage of ./
if (command.startsWith("./")) {
command = "run " + command.slice(2);
}
// Only split the first space
const commandArray = ParseCommand(command);
if (commandArray.length == 0) {
return;
}
const s = player.getCurrentServer();
/****************** Interactive Tutorial Terminal Commands ******************/
if (ITutorial.isRunning) {
const n00dlesServ = GetServer("n00dles");
if (n00dlesServ == null) {
throw new Error("Could not get n00dles server");
return;
}
switch (ITutorial.currStep) {
case iTutorialSteps.TerminalHelp:
if (commandArray.length === 1 && commandArray[0] == "help") {
iTutorialNextStep();
} else {
this.error("Bad command. Please follow the tutorial");
return;
}
break;
case iTutorialSteps.TerminalLs:
if (commandArray.length === 1 && commandArray[0] == "ls") {
iTutorialNextStep();
} else {
this.error("Bad command. Please follow the tutorial");
return;
}
break;
case iTutorialSteps.TerminalScan:
if (commandArray.length === 1 && commandArray[0] == "scan") {
iTutorialNextStep();
} else {
this.error("Bad command. Please follow the tutorial");
return;
}
break;
case iTutorialSteps.TerminalScanAnalyze1:
if (commandArray.length == 1 && commandArray[0] == "scan-analyze") {
iTutorialNextStep();
} else {
this.error("Bad command. Please follow the tutorial");
return;
}
break;
case iTutorialSteps.TerminalScanAnalyze2:
if (commandArray.length == 2 && commandArray[0] == "scan-analyze" && commandArray[1] === 2) {
iTutorialNextStep();
} else {
this.error("Bad command. Please follow the tutorial");
return;
}
break;
case iTutorialSteps.TerminalConnect:
if (commandArray.length == 2) {
if (
commandArray[0] == "connect" &&
(commandArray[1] == "n00dles" || commandArray[1] == n00dlesServ.hostname)
) {
iTutorialNextStep();
} else {
this.error("Wrong command! Try again!");
return;
}
} else {
this.error("Bad command. Please follow the tutorial");
return;
}
break;
case iTutorialSteps.TerminalAnalyze:
if (commandArray.length === 1 && commandArray[0] === "analyze") {
iTutorialNextStep();
} else {
this.error("Bad command. Please follow the tutorial");
return;
}
break;
case iTutorialSteps.TerminalNuke:
if (commandArray.length == 2 && commandArray[0] == "run" && commandArray[1] == "NUKE.exe") {
iTutorialNextStep();
} else {
this.error("Bad command. Please follow the tutorial");
return;
}
break;
case iTutorialSteps.TerminalManualHack:
if (commandArray.length == 1 && commandArray[0] == "hack") {
iTutorialNextStep();
} else {
this.error("Bad command. Please follow the tutorial");
return;
}
break;
case iTutorialSteps.TerminalHackingMechanics:
if (commandArray.length !== 1 || !["grow", "weaken", "hack"].includes(commandArray[0] + "")) {
this.error("Bad command. Please follow the tutorial");
return;
}
break;
case iTutorialSteps.TerminalGoHome:
if (commandArray.length == 1 && commandArray[0] == "home") {
iTutorialNextStep();
} else {
this.error("Bad command. Please follow the tutorial");
return;
}
break;
case iTutorialSteps.TerminalCreateScript:
if (commandArray.length == 2 && commandArray[0] == "nano" && commandArray[1] == "n00dles.script") {
iTutorialNextStep();
} else {
this.error("Bad command. Please follow the tutorial");
return;
}
break;
case iTutorialSteps.TerminalFree:
if (commandArray.length == 1 && commandArray[0] == "free") {
iTutorialNextStep();
} else {
this.error("Bad command. Please follow the tutorial");
return;
}
break;
case iTutorialSteps.TerminalRunScript:
if (commandArray.length == 2 && commandArray[0] == "run" && commandArray[1] == "n00dles.script") {
iTutorialNextStep();
} else {
this.error("Bad command. Please follow the tutorial");
return;
}
break;
case iTutorialSteps.ActiveScriptsToTerminal:
if (commandArray.length == 2 && commandArray[0] == "tail" && commandArray[1] == "n00dles.script") {
iTutorialNextStep();
} else {
this.error("Bad command. Please follow the tutorial");
return;
}
break;
default:
this.error("Please follow the tutorial, or click 'EXIT' if you'd like to skip it");
return;
}
}
/****************** END INTERACTIVE TUTORIAL ******************/
/* Command parser */
const commandName = commandArray[0];
if (typeof commandName === "number" || typeof commandName === "boolean") {
this.error(`Command ${commandArray[0]} not found`);
return;
}
const commands: {
[key: string]: (
terminal: ITerminal,
router: IRouter,
player: IPlayer,
server: BaseServer,
args: (string | number | boolean)[],
) => void;
} = {
"scan-analyze": scananalyze,
alias: alias,
analyze: analyze,
backdoor: backdoor,
buy: buy,
cat: cat,
cd: cd,
check: check,
clear: () => this.clear(),
cls: () => this.clear(),
connect: connect,
cp: cp,
download: download,
expr: expr,
free: free,
grow: grow,
hack: hack,
help: help,
home: home,
hostname: hostname,
kill: kill,
killall: killall,
ls: ls,
lscpu: lscpu,
mem: mem,
mv: mv,
nano: nano,
ps: ps,
rm: rm,
run: run,
scan: scan,
scp: scp,
sudov: sudov,
tail: tail,
top: top,
unalias: unalias,
vim: vim,
weaken: weaken,
wget: wget,
};
const f = commands[commandName.toLowerCase()];
if (!f) {
this.error(`Command ${commandArray[0]} not found`);
return;
}
f(this, router, player, s, commandArray.slice(1));
}
getProgressText(): string {
if (this.action === null) throw new Error("trying to get the progress text when there's no action");
return createProgressBarText({
progress: (this.action.time - this.action.timeLeft) / this.action.time,
totalTicks: 50,
});
}
}