Updated everything
This commit is contained in:
		@@ -1,95 +1,120 @@
 | 
			
		||||
export class ArgError extends Error {}
 | 
			
		||||
 | 
			
		||||
export class ArgParser {
 | 
			
		||||
	/**
 | 
			
		||||
	 * Create a unix-like argument parser to extract flags from the argument list. Can also create help messages.
 | 
			
		||||
	 * @param name {string} - Script name
 | 
			
		||||
	 * @param desc {string} - Help text desciption
 | 
			
		||||
	 * @param examples {string[]} - Help text examples
 | 
			
		||||
	 * @param argList {name: string, desc: string, flags: string[], type: string, default: any}[] - Array of CLI arguments
 | 
			
		||||
	 * @param allowUnknown {boolean} - Allow unknown flags
 | 
			
		||||
	 *
 | 
			
		||||
	 * @param {string} name - Script name
 | 
			
		||||
	 * @param {string} desc - Help description
 | 
			
		||||
	 * @param {(ArgParser | {name: string, desc: string, flags?: string[], optional?: boolean, default?: any})[]} argList - Array of CLI arguments
 | 
			
		||||
	 * @param {string[]} examples - Additional examples to display
 | 
			
		||||
	 */
 | 
			
		||||
	constructor(name, desc, examples, argList, allowUnknown = false) {
 | 
			
		||||
		this.name = name ?? 'example.js';
 | 
			
		||||
		this.description = desc ?? 'Example description';
 | 
			
		||||
		this.examples = examples || [`${argList.find(arg => !!arg.flags) ? '[OPTIONS] ' : ''}${argList.filter(arg => !arg.flags).map(arg => (arg.optional ? `[${arg.name.toUpperCase()}]` : arg.name.toUpperCase()) + (arg.extras ? '...' : '')).join(' ')}`];
 | 
			
		||||
		this.examples.push('--help');
 | 
			
		||||
		this.argList = argList || [];
 | 
			
		||||
		this.argList.push({name: 'help', desc: 'Display this help message', flags: ['-h', '--help'], type: 'bool'});
 | 
			
		||||
		this.allowUnknown = allowUnknown;
 | 
			
		||||
	constructor(name, desc, argList = [], examples = []) {
 | 
			
		||||
		this.name = name;
 | 
			
		||||
		this.desc = desc;
 | 
			
		||||
 | 
			
		||||
		// Arguments
 | 
			
		||||
		this.commands = argList.filter(arg => arg instanceof ArgParser);
 | 
			
		||||
		this.args = argList.filter(arg => !arg.flags || !arg.flags.length);
 | 
			
		||||
		this.flags = argList.filter(arg => !(arg instanceof ArgParser) && arg.flags && arg.flags.length);
 | 
			
		||||
		this.flags.push({name: 'help', desc: 'Display this help message', flags: ['-h', '--help'], default: false});
 | 
			
		||||
		this.defaults = argList.reduce((acc, arg) => ({...acc, [arg.name]: arg?.extras ? [] : arg.default ?? null}), {});
 | 
			
		||||
 | 
			
		||||
		// Examples
 | 
			
		||||
		this.examples = [
 | 
			
		||||
			...examples,
 | 
			
		||||
			`[OPTIONS] ${this.args.map(arg => (arg.optional ? `[${arg.name.toUpperCase()}]` : arg.name.toUpperCase()) + (arg.extras ? '...' : '')).join(' ')}`,
 | 
			
		||||
			this.commands.length ? `[OPTIONS] COMMAND` : null,
 | 
			
		||||
			`--help ${this.commands.length ? '[COMMAND]' : ''}`
 | 
			
		||||
		].filter(e => !!e);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Parse an array into an arguments dictionary using the configuration.
 | 
			
		||||
	 * @param args {string[]} - Array of arguments to be parsed
 | 
			
		||||
	 *
 | 
			
		||||
	 * @param {string[]} args - Array of arguments to be parsed
 | 
			
		||||
	 * @returns {object} - Dictionary of arguments with defaults applied
 | 
			
		||||
	 */
 | 
			
		||||
	parse(args) {
 | 
			
		||||
		// Parse arguments
 | 
			
		||||
		const queue = [...args], extra = [];
 | 
			
		||||
		const parsed = this.argList.reduce((acc, arg) => ({...acc, [arg.name]: arg.default ?? (arg.type == 'bool' ? false : null)}), {});
 | 
			
		||||
		// Flags
 | 
			
		||||
		let extras = [], parsed = {...this.defaults, '_error': []}, queue = [...args];
 | 
			
		||||
		while(queue.length) {
 | 
			
		||||
			let parse = queue.splice(0, 1)[0];
 | 
			
		||||
			if(parse[0] == '-') {
 | 
			
		||||
				// Check combined flags
 | 
			
		||||
				if(parse[1] != '-' && parse.length > 2) {
 | 
			
		||||
					parse = `-${parse[1]}`;
 | 
			
		||||
					queue = parse.substring(1).split('').map(a => `-${a}`).concat(queue);
 | 
			
		||||
			let arg = queue.splice(0, 1)[0];
 | 
			
		||||
			if(arg[0] == '-') { // Flags
 | 
			
		||||
				// Check for combined shorthand
 | 
			
		||||
				if(arg[1] != '-' && arg.length > 2) {
 | 
			
		||||
					queue = [...arg.substring(2).split('').map(a => `-${a}`), ...queue];
 | 
			
		||||
					arg = `-${arg[1]}`;
 | 
			
		||||
				}
 | 
			
		||||
				// Find & add flag
 | 
			
		||||
				const split = parse.split('=');
 | 
			
		||||
				const arg = this.argList.find(arg => arg.flags && arg.flags.includes(split[0] || parse));
 | 
			
		||||
				if(arg == null) {
 | 
			
		||||
					if(!this.allowUnknown) throw new ArgError(`Option unknown: ${parse}`);
 | 
			
		||||
					extra.push(parse);
 | 
			
		||||
				const combined = arg.split('=');
 | 
			
		||||
				const argDef = this.flags.find(flag => flag.flags.includes(combined[0] || arg));
 | 
			
		||||
				if(argDef == null) { // Not found, add to extras
 | 
			
		||||
					extras.push(arg);
 | 
			
		||||
					continue;
 | 
			
		||||
				}
 | 
			
		||||
				if(arg.name == 'help') throw new ArgError('Help');
 | 
			
		||||
				const value = arg.type == 'bool' ? true : split[1] || queue.splice(queue.findIndex(q => q[0] != '-'), 1)[0];
 | 
			
		||||
				if(value == null) throw new ArgError(`Option missing value: ${arg.name}`);
 | 
			
		||||
				parsed[arg.name] = value;
 | 
			
		||||
			} else { 
 | 
			
		||||
				// Save for required parsing
 | 
			
		||||
				extra.push(parse);
 | 
			
		||||
				const value = argDef.default === false ? true : argDef.default === true ? false : queue.splice(queue.findIndex(q => q[0] != '-'), 1)[0] || argDef.default;
 | 
			
		||||
				if(value == null) parsed['_error'].push(`Option missing value: ${arg.name}`);
 | 
			
		||||
				parsed[argDef.name] = value;
 | 
			
		||||
			} else { // Command
 | 
			
		||||
				const c = this.commands.find(command => command.name == arg);
 | 
			
		||||
				if(!!c) {
 | 
			
		||||
					const parsedCommand = c.parse(queue.splice(0, queue.length));
 | 
			
		||||
					Object.keys(parsedCommand).forEach(key => {
 | 
			
		||||
						if(parsed[key] != parsedCommand[key] && parsedCommand[key] == c.defaults[key])
 | 
			
		||||
							delete parsedCommand[key];
 | 
			
		||||
					});
 | 
			
		||||
					parsed = {
 | 
			
		||||
						...parsed,
 | 
			
		||||
						...parsedCommand,
 | 
			
		||||
						_command: c.name
 | 
			
		||||
					};
 | 
			
		||||
				} else extras.push(arg); // Not found, add to extras
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		// Arguments
 | 
			
		||||
		this.argList.filter(arg => !arg.flags && !arg.extras).forEach(arg => {
 | 
			
		||||
			if(!arg.optional && !extra.length) throw new ArgError(`Argument missing: ${arg.name.toUpperCase()}`);
 | 
			
		||||
			const value = extra.splice(0, 1)[0];
 | 
			
		||||
			if(value != null) parsed[arg.name] = value;
 | 
			
		||||
		this.args.filter(arg => !arg.extras).forEach(arg => {
 | 
			
		||||
			if(!arg.optional && !extras.length) parsed['_error'].push(`Argument missing: ${arg.name.toUpperCase()}`);
 | 
			
		||||
			if(extras.length) parsed[arg.name] = extras.splice(0, 1)[0];
 | 
			
		||||
		});
 | 
			
		||||
		// Extras
 | 
			
		||||
		const extraKey = this.argList.find(arg => arg.extras)?.name || 'extra';
 | 
			
		||||
		parsed[extraKey] = extra;
 | 
			
		||||
		const extraKey = this.args.find(arg => arg.extras)?.name || '_extra';
 | 
			
		||||
		parsed[extraKey] = extras;
 | 
			
		||||
		return parsed;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Create help message from the provided description, examples & argument list.
 | 
			
		||||
	 * @param message {string} - Message to display, defaults to the description
 | 
			
		||||
	 * @returns {string} - Help message 
 | 
			
		||||
	 * Create help message from the provided description & argument list.
 | 
			
		||||
	 *
 | 
			
		||||
	 * @param {string} message - Message to display, defaults to the description
 | 
			
		||||
	 * @param {string} command - Command help message to show
 | 
			
		||||
	 * @returns {string} - Help message
 | 
			
		||||
	 */
 | 
			
		||||
	help(msg) {
 | 
			
		||||
	help(message = '', command = '') {
 | 
			
		||||
		const spacer = (text) => Array(24 - text.length || 1).fill(' ').join('');
 | 
			
		||||
 | 
			
		||||
		// Help with specific command
 | 
			
		||||
		if(command) {
 | 
			
		||||
			const argParser = this.commands.find(parser => parser.name == command);
 | 
			
		||||
			if(!argParser) throw new Error(`${command.toUpperCase()} does not have a help`)
 | 
			
		||||
			return argParser.help(message);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Description
 | 
			
		||||
		let message = '\n\n' + (msg && msg.toLowerCase() != 'help' ? msg : this.description);
 | 
			
		||||
		// Usage
 | 
			
		||||
		if(this.examples.length) message += '\n\nUsage:\t' + this.examples.map(ex => `run ${this.name} ${ex}`).join('\n\t');
 | 
			
		||||
		let msg = `\n\n${message || this.desc}`;
 | 
			
		||||
		// Examples
 | 
			
		||||
		msg += '\n\nUsage:\t' + this.examples.map(ex => `run ${this.name} ${ex}`).join('\n\t');
 | 
			
		||||
		// Arguments
 | 
			
		||||
		const req = this.argList.filter(a => !a.flags);
 | 
			
		||||
		if(req.length) message += '\n\n\t' + req.map(arg => {
 | 
			
		||||
			const padding = 3 - ~~(arg.name.length / 8);
 | 
			
		||||
			return `${arg.name.toUpperCase()}${Array(padding).fill('\t').join('')} ${arg.desc}`;
 | 
			
		||||
		}).join('\n\t');
 | 
			
		||||
		if(this.args.length) msg += '\n\n\t' + this.args
 | 
			
		||||
			.map(arg => `${arg.name.toUpperCase()}${spacer(arg.name)}${arg.desc}`)
 | 
			
		||||
			.join('\n\t');
 | 
			
		||||
		// Flags
 | 
			
		||||
		const opts = this.argList.filter(a => a.flags);
 | 
			
		||||
		if(opts.length) message += '\n\nOptions:\n\t' + opts.map(a => {
 | 
			
		||||
			const flgs = a.flags.join(' ');
 | 
			
		||||
			const padding = 3 - ~~(flgs.length / 8);
 | 
			
		||||
			return `${flgs}${Array(padding).fill('\t').join('')} ${a.desc}`;
 | 
			
		||||
		msg += '\n\nOptions:\n\t' + this.flags.map(flag => {
 | 
			
		||||
			const flags = flag.flags.join(', ');
 | 
			
		||||
			return `${flags}${spacer(flags)}${flag.desc}`;
 | 
			
		||||
		}).join('\n\t');
 | 
			
		||||
		// Print final message
 | 
			
		||||
		return `${message}\n\n`;
 | 
			
		||||
		// Commands
 | 
			
		||||
		if(this.commands.length) msg += '\n\nCommands:\n\t' + this.commands
 | 
			
		||||
			.map(command => `${command.name}${spacer(command.name)}${command.desc}`)
 | 
			
		||||
			.join('\n\t');
 | 
			
		||||
		return `${msg}\n\n`;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,119 +0,0 @@
 | 
			
		||||
export class ArgError extends Error {}
 | 
			
		||||
 | 
			
		||||
export class ArgParser {
 | 
			
		||||
    /**
 | 
			
		||||
     * Create a unix-like argument parser to extract flags from the argument list. Can also create help messages.
 | 
			
		||||
     * @param name {string} - Script name
 | 
			
		||||
     * @param desc {string} - Help description
 | 
			
		||||
     * @param argList {(ArgParser || {name: string, desc: string, flags: string[], optional: boolean, default: boolean})[]} - Array of CLI arguments
 | 
			
		||||
     * @param examples {string[]} - Additional examples to display
 | 
			
		||||
     */
 | 
			
		||||
    constructor(ns, name, desc, argList = [], examples = []) {
 | 
			
		||||
        this.ns = ns;
 | 
			
		||||
        this.name = name;
 | 
			
		||||
        this.desc = desc;
 | 
			
		||||
 | 
			
		||||
        // Arguments
 | 
			
		||||
        this.commands = argList.filter(arg => arg instanceof ArgParser);
 | 
			
		||||
        this.args = argList.filter(arg => !arg.flags || !arg.flags.length);
 | 
			
		||||
        this.flags = argList.filter(arg => !(arg instanceof ArgParser) && arg.flags && arg.flags.length);
 | 
			
		||||
        this.flags.push({name: 'help', desc: 'Display this help message', flags: ['-h', '--help'], default: false});
 | 
			
		||||
        this.defaults = argList.reduce((acc, arg) => ({...acc, [arg.name]: arg?.extras ? [] : arg.default ?? null}), {});
 | 
			
		||||
 | 
			
		||||
        // Examples
 | 
			
		||||
        this.examples = [
 | 
			
		||||
            ...examples,
 | 
			
		||||
            `[OPTIONS] ${this.args.map(arg => (arg.optional ? `[${arg.name.toUpperCase()}]` : arg.name.toUpperCase()) + (arg.extras ? '...' : '')).join(' ')}`,
 | 
			
		||||
            this.commands.length ? `[OPTIONS] COMMAND` : null,
 | 
			
		||||
            `--help ${this.commands.length ? '[COMMAND]' : ''}`
 | 
			
		||||
        ].filter(e => !!e);
 | 
			
		||||
    }
 | 
			
		||||
    /**
 | 
			
		||||
     * Parse an array into an arguments dictionary using the configuration.
 | 
			
		||||
     * @param args {string[]} - Array of arguments to be parsed
 | 
			
		||||
     * @returns {object} - Dictionary of arguments with defaults applied
 | 
			
		||||
     */
 | 
			
		||||
    parse(args = this.ns.args) {
 | 
			
		||||
        // Parse arguments
 | 
			
		||||
        let extras = [], parsed = {...this.defaults}, queue = [...args];
 | 
			
		||||
        while(queue.length) {
 | 
			
		||||
            let arg = queue.splice(0, 1)[0];
 | 
			
		||||
            if(arg[0] == '-') { // Flags
 | 
			
		||||
                // Check for combined shorthand
 | 
			
		||||
                if(arg[1] != '-' && arg.length > 2) {
 | 
			
		||||
                    queue = [...arg.substring(2).split('').map(a => `-${a}`), ...queue];
 | 
			
		||||
                    arg = `-${arg[1]}`;
 | 
			
		||||
                }
 | 
			
		||||
                // Find & add flag
 | 
			
		||||
                const combined = arg.split('=');
 | 
			
		||||
                const argDef = this.flags.find(flag => flag.flags.includes(combined[0] || arg));
 | 
			
		||||
                if(argDef == null) { // Not found, add to extras
 | 
			
		||||
                    extras.push(arg);
 | 
			
		||||
                    continue;
 | 
			
		||||
                }
 | 
			
		||||
                const value = argDef.default === false ? true : argDef.default === true ? false : queue.splice(queue.findIndex(q => q[0] != '-'), 1)[0] || argDef.default;
 | 
			
		||||
                if(value == null) parsed['_error'] = `Option missing value: ${arg.name}`;
 | 
			
		||||
                parsed[argDef.name] = value;
 | 
			
		||||
            } else { // Command
 | 
			
		||||
                const c = this.commands.find(command => command.name == arg);
 | 
			
		||||
                if(!!c) {
 | 
			
		||||
                    const parsedCommand = c.parse(queue.splice(0, queue.length));
 | 
			
		||||
                    Object.keys(parsedCommand).forEach(key => {
 | 
			
		||||
                        if(parsed[key] != parsedCommand[key] && parsedCommand[key] == c.defaults[key])
 | 
			
		||||
                            delete parsedCommand[key];
 | 
			
		||||
                    });
 | 
			
		||||
                    parsed = {
 | 
			
		||||
                        ...parsed,
 | 
			
		||||
                        ...parsedCommand,
 | 
			
		||||
                        _command: c.name
 | 
			
		||||
                    };
 | 
			
		||||
                } else extras.push(arg); // Not found, add to extras
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        // Arguments
 | 
			
		||||
        this.args.filter(arg => !arg.extras).forEach(arg => {
 | 
			
		||||
            if(!arg.optional && !extras.length) parsed['_error'] = `Argument missing: ${arg.name.toUpperCase()}`;
 | 
			
		||||
            parsed[arg.name] = extras.splice(0, 1)[0];
 | 
			
		||||
        });
 | 
			
		||||
        // Extras
 | 
			
		||||
        const extraKey = this.args.find(arg => arg.extras)?.name || '_extra';
 | 
			
		||||
        parsed[extraKey] = extras;
 | 
			
		||||
        return parsed;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Create help message from the provided description & argument list.
 | 
			
		||||
     * @param message {string} - Message to display, defaults to the description
 | 
			
		||||
     * @param command {string} - Command help message to show
 | 
			
		||||
     * @returns {string} - Help message
 | 
			
		||||
     */
 | 
			
		||||
    help(message = '', command = '') {
 | 
			
		||||
        const spacer = (text) => Array(24 - text.length || 1).fill(' ').join('');
 | 
			
		||||
 | 
			
		||||
        // Help with specific command
 | 
			
		||||
        if(command) {
 | 
			
		||||
            const argParser = this.commands.find(parser => parser.name == command);
 | 
			
		||||
            if(!argParser) throw new Error(`${command.toUpperCase()} does not have a help`)
 | 
			
		||||
            return argParser.help(message);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Description
 | 
			
		||||
        let msg = `\n\n${message || this.desc}`;
 | 
			
		||||
        // Examples
 | 
			
		||||
        msg += '\n\nUsage:\t' + this.examples.map(ex => `run ${this.name} ${ex}`).join('\n\t');
 | 
			
		||||
        // Arguments
 | 
			
		||||
        if(this.args.length) msg += '\n\n\t' + this.args
 | 
			
		||||
            .map(arg => `${arg.name.toUpperCase()}${spacer(arg.name)}${arg.desc}`)
 | 
			
		||||
            .join('\n\t');
 | 
			
		||||
        // Flags
 | 
			
		||||
        msg += '\n\nOptions:\n\t' + this.flags.map(flag => {
 | 
			
		||||
            const flags = flag.flags.join(', ');
 | 
			
		||||
            return `${flags}${spacer(flags)}${flag.desc}`;
 | 
			
		||||
        }).join('\n\t');
 | 
			
		||||
        // Commands
 | 
			
		||||
        if(this.commands.length) msg += '\n\nCommands:\n\t' + this.commands
 | 
			
		||||
            .map(command => `${command.name}${spacer(command.name)}${command.desc}`)
 | 
			
		||||
            .join('\n\t');
 | 
			
		||||
        this.ns.tprint(`${msg}\n\n`);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -4,8 +4,9 @@ export class Logger {
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Create a nicer log with a banner.
 | 
			
		||||
	 * @param ns {NS} - BitBurner API
 | 
			
		||||
	 * @param lineFns {Function[]} - Functions to generate a line (Seperated by a linebreak)
 | 
			
		||||
	 *
 | 
			
		||||
	 * @param {NS} ns - BitBurner API
 | 
			
		||||
	 * @param {Function[]} lineFns - Functions to generate a line (Seperated by a linebreak)
 | 
			
		||||
	 */
 | 
			
		||||
	constructor(ns, lineFns = []) {
 | 
			
		||||
		this.ns = ns;
 | 
			
		||||
@@ -16,8 +17,9 @@ export class Logger {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Add red error message to logs
 | 
			
		||||
	 * @param message {string} - Text that will be added
 | 
			
		||||
	 * Add red error message to logs.
 | 
			
		||||
	 *
 | 
			
		||||
	 * @param {string} message - Text that will be added
 | 
			
		||||
	 */
 | 
			
		||||
	error(message) { this.log(`ERROR: ${message}`); }
 | 
			
		||||
 | 
			
		||||
@@ -29,7 +31,7 @@ export class Logger {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Print the header using the provided functions
 | 
			
		||||
	 * Print the header using the provided functions.
 | 
			
		||||
	 */
 | 
			
		||||
	header() {
 | 
			
		||||
		this.lineBreak();
 | 
			
		||||
@@ -40,8 +42,9 @@ export class Logger {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Add message to the logs
 | 
			
		||||
	 * @param message {string} - Text that will be added
 | 
			
		||||
	 * Add message to the logs.
 | 
			
		||||
	 *
 | 
			
		||||
	 * @param {string} message - Text that will be added
 | 
			
		||||
	 */
 | 
			
		||||
	log(message = '') {
 | 
			
		||||
		this.ns.clearLog();
 | 
			
		||||
@@ -52,8 +55,9 @@ export class Logger {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Add orange warning to the logs
 | 
			
		||||
	 * @param message {string} - Text that will be added
 | 
			
		||||
	 * Add orange warning to the logs.
 | 
			
		||||
	 *
 | 
			
		||||
	 * @param {string} message - Text that will be added
 | 
			
		||||
	 */
 | 
			
		||||
	warn(message) { this.log(`WARN: ${message}`); }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,42 +1,60 @@
 | 
			
		||||
/**
 | 
			
		||||
 * Scan the entire network for the best device to hack.
 | 
			
		||||
 * @param ns {NS} - BitBurner API
 | 
			
		||||
 * @returns {string[]} - Sorted list of targets to hack based on financial return
 | 
			
		||||
 * Add CSS to DOM.
 | 
			
		||||
 *
 | 
			
		||||
 * @param {string} id - An ID so we can make sure we only inject it once
 | 
			
		||||
 * @param {string} css - CSS to inject
 | 
			
		||||
 */
 | 
			
		||||
export function bestTarget(ns) {
 | 
			
		||||
	const [devices, network] = scanNetwork(ns, 'home');
 | 
			
		||||
	return devices.map(d => ns.getServer(d)).filter(d => d.hasAdminRights).map(d => ({
 | 
			
		||||
		...d,
 | 
			
		||||
		moneyAMinute: (ns.hackAnalyze(d.hostname) * ns.getServerMaxMoney(d.hostname)) * ((60 / (ns.getHackTime(d.hostname) / 1000)) * ns.hackAnalyzeChance(d.hostname))}
 | 
			
		||||
	)).sort((a, b) => {
 | 
			
		||||
		if(a.moneyAMinute < b.moneyAMinute) return 1;
 | 
			
		||||
		if(a.moneyAMinute > b.moneyAMinute) return -1;
 | 
			
		||||
		return 0;
 | 
			
		||||
export function addCSS(id, css) {
 | 
			
		||||
	const doc = eval('document');
 | 
			
		||||
	id = `dynamic-css-${id}`;
 | 
			
		||||
	const exists = doc.querySelector(`#${id}`);
 | 
			
		||||
	if(exists) exists.outerHTML = '';
 | 
			
		||||
	doc.head.insertAdjacentHTML('beforeend', `<style id="${id}">${css}</style`);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Format number to look like a dollar value ($1,000.00).
 | 
			
		||||
 *
 | 
			
		||||
 * @param {number} num - Number to format
 | 
			
		||||
 * @returns {string} - formatted value with dollar sign
 | 
			
		||||
 */
 | 
			
		||||
export function toCurrency(num) {
 | 
			
		||||
	return Number(num).toLocaleString('en-US', {
 | 
			
		||||
		style: 'currency',
 | 
			
		||||
		currency: 'USD',
 | 
			
		||||
	});
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Copy a file & scan it for dependencies copying them as well.
 | 
			
		||||
 * @param ns {NS} - BitBurner API
 | 
			
		||||
 * @param src {string} - File to scan & copy
 | 
			
		||||
 * @param device {string} - Device to copy files to
 | 
			
		||||
 * @returns {string[]} - Array of coppied files
 | 
			
		||||
 * Injects HTML into the terminal as a new line.
 | 
			
		||||
 *
 | 
			
		||||
 * **Disclaimer:** React will wipe out anything injected by this function.
 | 
			
		||||
 *
 | 
			
		||||
 * @param {string} html - HTML to inject into terminal
 | 
			
		||||
 * @param {boolean} wrap - Wrap in a list-item & paragraph to match default style
 | 
			
		||||
 */
 | 
			
		||||
export async function copyWithDependencies(ns, src, device) {
 | 
			
		||||
	const queue = [src], found = [src];
 | 
			
		||||
	while(queue.length) {
 | 
			
		||||
		const file = queue.splice(0, 1)[0];
 | 
			
		||||
		const imports = new RegExp(/from ["']\.?(\/.+)["']/g);
 | 
			
		||||
		const script = await ns.read(file);
 | 
			
		||||
		let match;
 | 
			
		||||
		while((match = imports.exec(script)) != null) {
 | 
			
		||||
			const path = `${match[1]}.js`;
 | 
			
		||||
			queue.push(path);
 | 
			
		||||
			found.push(path);
 | 
			
		||||
export function htmlPrint(html, wrap = true) {
 | 
			
		||||
	setTimeout(() => {
 | 
			
		||||
		const doc = eval('document');
 | 
			
		||||
		if(wrap) {
 | 
			
		||||
			const liClass = doc.querySelector('#terminal li').classList.value;
 | 
			
		||||
			const pClass = doc.querySelector('#terminal li p').classList.value;
 | 
			
		||||
			html = `<li class="${liClass}"><p class="${pClass}">${html}</p></li>`
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	await ns.scp(found, device);
 | 
			
		||||
	return found.reverse();
 | 
			
		||||
		eval('document').getElementById('terminal').insertAdjacentHTML('beforeend', html)
 | 
			
		||||
	}, 25);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Calculate the maximum number of threads a script can be executed with.
 | 
			
		||||
 *
 | 
			
		||||
 * @param {NS} ns - BitBurner API
 | 
			
		||||
 * @param {string} script - Full path to script
 | 
			
		||||
 * @param {string} server - Server script will run on
 | 
			
		||||
 * @returns {number} - Number of threads the server will be able to support
 | 
			
		||||
 */
 | 
			
		||||
export function maxThreads(ns, script, server = ns.getHostname()) {
 | 
			
		||||
	return ~~(ns.getServerMaxRam(server) / ns.getScriptRam(script, ns.getHostname()))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -46,10 +64,10 @@ export async function copyWithDependencies(ns, src, device) {
 | 
			
		||||
 *
 | 
			
		||||
 * `/script/test.js          [||||||||||----------]  50% (24.2 MB/s)`
 | 
			
		||||
 *
 | 
			
		||||
 * @param ns {NS} - BitBurner API
 | 
			
		||||
 * @param name {string} - Name to display at the begging of bar
 | 
			
		||||
 * @param showSpeed {boolean} - Show the speed in the progress bar
 | 
			
		||||
 * @param time {number} - Time it takes for bar to fill
 | 
			
		||||
 * @param {NS} ns - BitBurner API
 | 
			
		||||
 * @param {string} name - Name to display at the begging of bar
 | 
			
		||||
 * @param {boolean} showSpeed - Show the speed in the progress bar
 | 
			
		||||
 * @param {number} time - Time it takes for bar to fill
 | 
			
		||||
 */
 | 
			
		||||
export async function progressBar(ns, name, showSpeed = true, time = Math.random() + 0.5) {
 | 
			
		||||
	const text = (percentage, speed) => {
 | 
			
		||||
@@ -77,8 +95,9 @@ export async function progressBar(ns, name, showSpeed = true, time = Math.random
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * **Impure:** Prune tree down to keys which pass function
 | 
			
		||||
 * @param tree {object} - Tree to search
 | 
			
		||||
 * @param fn {(key: string) => boolean} - Function to test each key with
 | 
			
		||||
 *
 | 
			
		||||
 * @param {Object} tree - Tree to search
 | 
			
		||||
 * @param {(key: string) => boolean} fn - Function to test each key with
 | 
			
		||||
 * @returns {boolean} - True if a match was found
 | 
			
		||||
 */
 | 
			
		||||
export function pruneTree(tree, fn) {
 | 
			
		||||
@@ -91,56 +110,65 @@ export function pruneTree(tree, fn) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Scan the network of a given device.
 | 
			
		||||
 * @param ns {NS} - BitBurner API
 | 
			
		||||
 * @param device {string} - Device network that will be scanned
 | 
			
		||||
 * @param maxDepth - Depth to scan to
 | 
			
		||||
 * @returns {[string[], Object]} - A tuple including an array of discovered devices & a tree of the network
 | 
			
		||||
 * Pause for a random amount of time.
 | 
			
		||||
 * @param {number} min - minimum amount of time to wait after printing text
 | 
			
		||||
 * @param {number} max - maximum amount of time to wait after printing text
 | 
			
		||||
 */
 | 
			
		||||
export function scanNetwork(ns, device = ns.getHostname(), maxDepth = Infinity) {
 | 
			
		||||
	let discovered = [device];
 | 
			
		||||
	function scan (device, depth = 1) {
 | 
			
		||||
		if(depth > maxDepth) return {};
 | 
			
		||||
		const localTargets = ns.scan(device).filter(newDevice => !discovered.includes(newDevice));
 | 
			
		||||
		discovered = [...discovered, ...localTargets];
 | 
			
		||||
		return localTargets.reduce((acc, device) => ({...acc, [device]: scan(device, depth + 1)}), {});
 | 
			
		||||
	}
 | 
			
		||||
	const network = scan(device);
 | 
			
		||||
	return [discovered.slice(1), network];
 | 
			
		||||
export function randomSleep(min = 0.5, max = 1.5) {
 | 
			
		||||
	return new Promise(res => setTimeout(res, ~~(Math.random() * (max * 1000 - min * 1000)) + min * 1000));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Converts function into HTML friendly string with it's arguments. Meant to be used
 | 
			
		||||
 * with `onclick` to execute code.
 | 
			
		||||
 *
 | 
			
		||||
 * @param {Function} fn - function that will be serialized
 | 
			
		||||
 * @param {...any} args - Arguments passed to function
 | 
			
		||||
 * @returns {string} - Serialized function with arguments: "(function(arg1, arg2, ...) {...})(arg1, arg2, ...)"
 | 
			
		||||
 */
 | 
			
		||||
export function serializeFunction(fn, ...args) {
 | 
			
		||||
	let serialized = fn.toString().replace(/function .+\W?\(/, 'function(');
 | 
			
		||||
	serialized = `(${serialized})(${args.map(a => JSON.stringify(a)).join()})`;
 | 
			
		||||
	serialized = serialized.replace(/"/g, '"');
 | 
			
		||||
	serialized = serialized.replace(/'/g, ''');
 | 
			
		||||
	return serialized;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Print text to the terminal & then delay for a random amount of time to emulate execution time.
 | 
			
		||||
 * @param ns {NS} - BitBurner API
 | 
			
		||||
 * @param message {string} - Text to display
 | 
			
		||||
 * @param min {number} - minimum amount of time to wait after printing text
 | 
			
		||||
 * @param max {number} - maximum amount of time to wait after printing text
 | 
			
		||||
 *
 | 
			
		||||
 * @param {NS} ns - BitBurner API
 | 
			
		||||
 * @param {string} message - Text to display
 | 
			
		||||
 * @param {boolean} first - Pause first or wait until text is displayed
 | 
			
		||||
 * @param {number} min - minimum amount of time to wait after printing text
 | 
			
		||||
 * @param {number} max - maximum amount of time to wait after printing text
 | 
			
		||||
 */
 | 
			
		||||
export async function slowPrint(ns, message, min = 0.5, max = 1.5) {
 | 
			
		||||
	const time = ~~(Math.random() * (max * 1000 - min * 1000)) + min * 1000;
 | 
			
		||||
export async function slowPrint(ns, message, first = false, min = 0.5, max = 0.5) {
 | 
			
		||||
	if(first) await randomSleep(min, max);
 | 
			
		||||
	ns.tprint(message);
 | 
			
		||||
	await ns.sleep(time);
 | 
			
		||||
	await randomSleep(min, max);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Write a command to the terminal.
 | 
			
		||||
 * @param command {string} - Command that will be run
 | 
			
		||||
 * @returns {Promise<string>} - Command line response
 | 
			
		||||
 *
 | 
			
		||||
 * @param {string} command - Command that will be run
 | 
			
		||||
 * @returns {Promise<string[]>} - Any new output
 | 
			
		||||
 */
 | 
			
		||||
export function terminal(command) {
 | 
			
		||||
	// Get the terminal
 | 
			
		||||
	const terminalInput = document.getElementById("terminal-input");
 | 
			
		||||
	const doc = eval('document');
 | 
			
		||||
	const terminalInput = doc.getElementById("terminal-input");
 | 
			
		||||
	const handler = Object.keys(terminalInput)[1];
 | 
			
		||||
 | 
			
		||||
	// Send command
 | 
			
		||||
	terminalInput.value = command; // Enter the command
 | 
			
		||||
	terminalInput[handler].onChange({target:terminalInput}); // React on change
 | 
			
		||||
	terminalInput[handler].onChange({target: terminalInput}); // React on change
 | 
			
		||||
	terminalInput[handler].onKeyDown({key: 'Enter', preventDefault: () => null}); // Enter 'keystroke'
 | 
			
		||||
 | 
			
		||||
	// Return any new terminal output
 | 
			
		||||
	return new Promise(res => setTimeout(() => {
 | 
			
		||||
		const terminalOutput = Array.from(eval('document')
 | 
			
		||||
			.querySelectorAll('#terminal li p')).map(out => out.innerText);
 | 
			
		||||
		const terminalOutput = Array.from(doc.querySelectorAll('#terminal li p')).map(out => out.innerText);
 | 
			
		||||
		const i = terminalOutput.length - terminalOutput.reverse().findIndex(o => o.indexOf(command) != -1);
 | 
			
		||||
		res(terminalOutput.reverse().slice(i));
 | 
			
		||||
	}, 25));
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user