This commit is contained in:
monoid 2022-08-16 23:18:41 +09:00
parent 41e3b3fbcd
commit dd9664d4ee
19 changed files with 1107 additions and 325 deletions

View File

@ -16,322 +16,23 @@ module.exports = {
plugins: ["@typescript-eslint"],
ignorePatterns: ['NetscriptDefinitions.d.ts', '*.js'],
rules: {
"accessor-pairs": [
"@typescript-eslint/array-type": ["error", { "default": "array-simple" }],
"@typescript-eslint/ban-ts-comment": ["off"],
"@typescript-eslint/explicit-member-accessibility": ["off"],
"@typescript-eslint/explicit-module-boundary-types": ["off"],
"@typescript-eslint/no-non-null-assertion": ["off"],
"@typescript-eslint/no-use-before-define": ["off"],
"@typescript-eslint/no-parameter-properties": ["off"],
"@typescript-eslint/no-unused-vars": [
"error",
{
setWithoutGet: true,
getWithoutSet: false,
},
{ "argsIgnorePattern": "^_", "varsIgnorePattern": "^_" }
],
"array-bracket-newline": ["off"],
"array-bracket-spacing": ["off"],
"array-callback-return": ["off"],
"array-element-newline": ["off"],
"arrow-body-style": ["off"],
"arrow-parens": ["off"],
"arrow-spacing": ["off"],
"block-scoped-var": ["off"],
"block-spacing": ["off"],
"brace-style": ["off"],
"callback-return": ["error"],
camelcase: ["off"],
"capitalized-comments": ["off"],
"class-methods-use-this": ["off"],
complexity: ["off"],
"consistent-return": ["off"],
"consistent-this": ["off"],
"constructor-super": ["error"],
curly: ["off"],
"default-case": ["off"],
"dot-notation": ["off"],
"eol-last": ["off"],
eqeqeq: ["off"],
"for-direction": ["error"],
"func-call-spacing": ["off"],
"func-name-matching": ["error"],
"func-names": ["off", "never"],
"func-style": ["off"],
"function-paren-newline": ["off"],
"getter-return": [
"error",
{
allowImplicit: false,
},
],
"global-require": ["off"],
"guard-for-in": ["off"],
"handle-callback-err": ["error"],
"id-blacklist": ["error"],
"id-length": ["off"],
"id-match": ["error"],
indent: ["off"],
"indent-legacy": ["off"],
"init-declarations": ["off"],
"key-spacing": ["off"],
"keyword-spacing": ["off"],
"line-comment-position": ["off"],
"linebreak-style": [
"off", // Line endings automatically converted to LF on git commit so probably shouldn't care about it here
],
"lines-around-comment": ["off"],
"lines-around-directive": ["error"],
"lines-between-class-members": ["error"],
"max-depth": ["off"],
"max-len": ["off"],
"max-lines": ["off"],
"max-nested-callbacks": ["error"],
"max-params": ["off"],
"max-statements": ["off"],
"max-statements-per-line": ["off"],
"multiline-comment-style": ["off", "starred-block"],
"multiline-ternary": ["off", "never"],
"new-cap": ["off"],
"new-parens": ["off"],
"newline-after-var": ["off"],
"newline-before-return": ["off"],
"newline-per-chained-call": ["off"],
"no-alert": ["error"],
"no-array-constructor": ["error"],
"no-await-in-loop": ["off"],
"no-bitwise": ["off"],
"no-buffer-constructor": ["error"],
"no-caller": ["error"],
"no-case-declarations": ["error"],
"no-catch-shadow": ["error"],
"no-class-assign": ["error"],
"no-compare-neg-zero": ["error"],
"no-confusing-arrow": ["error"],
"no-console": ["off"],
"no-const-assign": ["error"],
"no-constant-condition": [
"error",
{
checkLoops: false,
},
],
"no-continue": ["off"],
"no-control-regex": ["error"],
"no-debugger": ["error"],
"no-delete-var": ["error"],
"no-div-regex": ["error"],
"no-dupe-args": ["error"],
"no-dupe-class-members": ["error"],
"no-dupe-keys": ["error"],
"no-duplicate-case": ["error"],
"no-duplicate-imports": [
"error",
{
includeExports: true,
},
],
"no-else-return": ["off"],
"no-empty": [
"off",
{
allowEmptyCatch: false,
},
],
"no-empty-character-class": ["error"],
"no-empty-function": ["off"],
"no-empty-pattern": ["error"],
"no-eq-null": ["off"],
"no-ex-assign": ["off"],
"no-extra-boolean-cast": ["error"],
"no-extra-parens": ["off"],
"no-extra-semi": ["off"],
"no-eval": ["off"],
"no-extend-native": ["off"],
"no-extra-bind": ["error"],
"no-extra-label": ["error"],
"no-fallthrough": ["off"],
"no-floating-decimal": ["off"],
"no-func-assign": ["error"],
"no-global-assign": ["error"],
"no-implicit-coercion": ["off"],
"no-implicit-globals": ["error"],
"no-implied-eval": ["error"],
"no-inline-comments": ["off"],
"no-inner-declarations": ["off", "both"],
"no-invalid-regexp": ["error"],
"no-invalid-this": ["off"],
"no-irregular-whitespace": [
"error",
{
skipStrings: false,
skipComments: false,
skipRegExps: false,
skipTemplates: false,
},
],
"no-iterator": ["error"],
"no-label-var": ["error"],
"no-labels": ["off"],
"no-lone-blocks": ["error"],
"no-lonely-if": ["off"],
"no-loop-func": ["off"],
"no-magic-numbers": ["off"],
"no-mixed-operators": ["off"],
"no-mixed-requires": ["error"],
"no-mixed-spaces-and-tabs": ["off"],
"no-multi-assign": ["off"],
"no-multi-spaces": ["off"],
"no-multi-str": ["error"],
"no-multiple-empty-lines": [
"off",
{
max: 1,
},
],
"no-native-reassign": ["error"],
"no-negated-condition": ["off"],
"no-negated-in-lhs": ["error"],
"no-nested-ternary": ["off"],
"no-new": ["error"],
"no-new-func": ["error"],
"no-new-object": ["error"],
"no-new-require": ["error"],
"no-new-symbol": ["error"],
"no-new-wrappers": ["error"],
"no-octal": ["error"],
"no-octal-escape": ["error"],
"no-obj-calls": ["error"],
"no-param-reassign": ["off"],
"no-path-concat": ["error"],
"no-plusplus": ["off"],
"no-process-env": ["off"],
"no-process-exit": ["error"],
"no-proto": ["error"],
"no-prototype-builtins": ["off"],
"no-redeclare": ["off"],
"no-regex-spaces": ["error"],
"no-restricted-globals": ["error"],
"no-restricted-imports": ["error"],
"no-restricted-modules": ["error"],
"no-restricted-properties": [
"off",
{
object: "console",
property: "log",
message: "'log' is too general, use an appropriate level when logging.",
},
],
"no-restricted-syntax": ["error"],
"no-return-assign": ["off"],
"no-return-await": ["error"],
"no-script-url": ["error"],
"no-self-assign": [
"error",
{
props: false,
},
],
"no-self-compare": ["error"],
"no-sequences": ["error"],
"no-shadow": ["off"],
"no-shadow-restricted-names": ["error"],
"no-spaced-func": ["off"],
"no-sparse-arrays": ["error"],
"no-sync": ["error"],
"no-tabs": ["error"],
"no-template-curly-in-string": ["error"],
"no-ternary": ["off"],
"no-this-before-super": ["off"],
"no-throw-literal": ["error"],
"no-trailing-spaces": ["off"],
"no-undef": ["off"],
"no-undef-init": ["error"],
"no-undefined": ["off"],
"no-underscore-dangle": ["off"],
"no-unexpected-multiline": ["error"],
"no-unmodified-loop-condition": ["error"],
"no-unneeded-ternary": ["off"],
"no-unreachable": ["off"],
"no-unsafe-finally": ["error"],
"no-unsafe-negation": ["error"],
"no-unused-expressions": ["off"],
"no-unused-labels": ["error"],
"no-unused-vars": ["off"],
"no-use-before-define": ["off"],
"no-useless-call": ["off"],
"no-useless-computed-key": ["error"],
"no-useless-concat": ["off"],
"no-useless-constructor": ["error"],
"no-useless-escape": ["off"],
"no-useless-rename": [
"error",
{
ignoreDestructuring: false,
ignoreExport: false,
ignoreImport: false,
},
],
"no-useless-return": ["off"],
"no-var": ["off"],
"no-void": ["off"],
"no-warning-comments": ["off"],
"no-whitespace-before-property": ["error"],
"no-with": ["error"],
"nonblock-statement-body-position": ["off", "below"],
"object-curly-newline": ["off"],
"object-curly-spacing": ["off"],
"object-property-newline": ["off"],
"object-shorthand": ["off"],
"one-var": ["off"],
"one-var-declaration-per-line": ["off"],
"operator-assignment": ["off"],
"operator-linebreak": ["off", "none"],
"padded-blocks": ["off"],
"padding-line-between-statements": ["error"],
"prefer-arrow-callback": ["off"],
"prefer-const": ["off"],
"prefer-destructuring": ["off"],
"prefer-numeric-literals": ["error"],
"prefer-promise-reject-errors": ["off"],
"prefer-reflect": ["off"],
"prefer-rest-params": ["off"],
"prefer-spread": ["off"],
"prefer-template": ["off"],
"quote-props": ["off"],
quotes: ["off"],
radix: ["off", "as-needed"],
"require-await": ["off"],
"require-jsdoc": ["off"],
"require-yield": ["error"],
"rest-spread-spacing": ["error", "never"],
semi: ["warn", "never"],
"semi-spacing": ["off"],
"semi-style": ["error", "last"],
"sort-imports": ["off"],
"sort-keys": ["off"],
"sort-vars": ["off"],
"space-before-blocks": ["off"],
"space-before-function-paren": ["off"],
"space-in-parens": ["off"],
"space-infix-ops": ["off"],
"space-unary-ops": ["off"],
"spaced-comment": ["off"],
strict: ["off"],
"switch-colon-spacing": [
"error",
{
after: true,
before: false,
},
],
"symbol-description": ["error"],
"template-curly-spacing": ["error"],
"template-tag-spacing": ["error"],
"unicode-bom": ["error", "never"],
"use-isnan": ["error"],
"valid-jsdoc": ["off"],
"valid-typeof": ["error"],
"vars-on-top": ["off"],
"wrap-iife": ["error", "any"],
"wrap-regex": ["off"],
"yield-star-spacing": ["error", "before"],
yoda: ["error", "never"],
"@typescript-eslint/explicit-module-boundary-types": "off",
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/ban-ts-ignore": ["off"],
"@typescript-eslint/no-empty-function": ["off"],
"no-return-await": "error",
"require-await": "error",
"no-async-promise-executor": "error",
"no-constant-condition": ["off"]
},
overrides: [
{

View File

@ -6,9 +6,12 @@
"eslint.workingDirectories": ["./src"],
"editor.codeActionsOnSave": {"source.fixAll.eslint": true},
"bitburner.authToken": "MOG9GEp4MGcw4aupPiqC6C9kd8jtFR2JgWcuS9JMyJjYfRCv0sqWdoB1H0G7HMQY",
"bitburner.fileWatcher.enable": true,
// Bitburner Extension Settings
"bitburner.scriptRoot": "./dist/",
// Autosave Settings
"files.autoSave": "off",

37
build.js Normal file
View File

@ -0,0 +1,37 @@
const fs = require("fs");
const path = require("path");
const esbuild = require("esbuild");
const yargs = require("yargs");
yargs.scriptName("builder")
.usage("$0 <cmd> [args]")
.command("watch","watch and build",(yargs)=>{
},(argv)=>{
build({
watch:true,
});
})
.command("build","build",(yargs)=>{},
(argv)=>{
build();
})
.help()
.argv;
async function build(options){
options = options || {};
options.watch = options.watch || false;
let dir = fs.readdirSync("src");
dir = dir.filter(x=>x.endsWith(".ts"));
await esbuild.build({
entryPoints:dir,
bundle:true,
write: true,
treeShaking: true,
watch: options.watch,
platform: "neutral",
target:"es2020",
outdir:"./dist",
});
}

View File

@ -4,8 +4,7 @@
"description": "",
"main": "index.js",
"scripts": {
"watch": "npx tsc -w",
"lint": "eslint . --ext .ts",
"build": "node build.js",
"defs": "node ./updateDefs.js"
},
"repository": {
@ -18,12 +17,13 @@
},
"homepage": "https://github.com/SlyCedix/bitburner-typescript-template#readme",
"devDependencies": {
"@types/lodash": "^4.14.178",
"@types/node": "^16.4.3",
"@types/yargs": "^17.0.11",
"@typescript-eslint/eslint-plugin": "^4.28.4",
"@typescript-eslint/parser": "^4.28.4",
"eslint": "^7.31.0",
"ts-node": "^9.1.1",
"typescript": "^4.3.5"
"esbuild": "^0.15.3"
},
"dependencies": {
"yargs": "^17.5.1"
}
}
}

15
src/auto-hacking.ts Normal file
View File

@ -0,0 +1,15 @@
import { selectHackableServerList, hackServer } from "./lib/hack"
import {isRootedServer} from "./lib/servers"
import {NS} from "@ns"
/** @param {NS} ns */
// eslint-disable-next-line require-await
export async function main(ns: NS): Promise<void> {
//ns.disableLog("ALL")
const servers = selectHackableServerList(ns).filter(x => !isRootedServer(x))
servers.forEach(x => {
ns.tprint(`hack ${x.hostname}`)
hackServer(ns, x)
})
}

6
src/get-share-power.ts Normal file
View File

@ -0,0 +1,6 @@
import {NS} from "@ns"
// eslint-disable-next-line require-await
export async function main(ns:NS):Promise<void>{
ns.tprint(ns.getSharePower());
}

178
src/hacknet-daemon.ts Normal file
View File

@ -0,0 +1,178 @@
import {NS} from "@ns"
import { parse } from "./lib/flag";
import {range} from "./util/range";
class Account{
value: number;
timer?: NodeJS.Timer;
constructor(value: number){
this.value = value;
this.timer = undefined;
}
startIncrement(ns:NS, rate: number): void{
this.timer = setInterval(()=>{
this.value += ns.hacknet.getNodeStats().production * rate;
},1000);
}
stopIncrement(): void{
clearInterval(this.timer)
}
}
export function getNodeProduction(level: number, ram: number, cores: number, mult: number, bitMult:number): number{
const moneyGainPerLevel = 1.5;
const levelMult = level * moneyGainPerLevel;
const ramMult = Math.pow(1.035, ram - 1);
const coreMult = ((cores + 5) / 6);
return levelMult * ramMult * coreMult * mult * bitMult;
}
interface HacknetNodeComponent<T> {
level: T;
core: T;
ram: T;
}
type ComponentKind = "level" | "core" | "ram";
export interface CEInfo {
upgradeCost : HacknetNodeComponent<number>;
production : HacknetNodeComponent<number>;
costEffective : HacknetNodeComponent<number>;
}
function getCostEffective(ns:NS, nodeIndex: number, n:number): CEInfo{
const coreUpgradeCost = ns.hacknet.getCoreUpgradeCost(nodeIndex,n);
const ramUpgradeCost = ns.hacknet.getRamUpgradeCost(nodeIndex,n);
const levelUpgradeCost = ns.hacknet.getLevelUpgradeCost(nodeIndex,n);
const mult = ns.getHacknetMultipliers().production;
const bitMult = 1//ns.getBitNodeMultipliers().HacknetNodeMoney;
const stat = ns.hacknet.getNodeStats(nodeIndex);
const coreProduction = getNodeProduction(stat.level, stat.ram, stat.cores + n, mult, bitMult) - stat.production;
const levelProduction = getNodeProduction(stat.level + n, stat.ram, stat.cores, mult, bitMult) - stat.production;
const ramProduction = getNodeProduction(stat.level, stat.ram * Math.pow(2,n), stat.cores, mult, bitMult) - stat.production;
return {
upgradeCost:{
core: coreUpgradeCost,
ram: ramUpgradeCost,
level: levelUpgradeCost,
},
production:{
core: coreProduction,
ram: ramProduction,
level: levelProduction,
},
costEffective:{
core: coreProduction/coreUpgradeCost,
ram: ramProduction/ramUpgradeCost,
level: levelProduction/levelUpgradeCost
}
}
}
function createUpgradeAction(ns:NS, index: number, n: number, kind:ComponentKind):(account: Account)=>void{
return (account: Account)=>{
switch (kind) {
case "core":
account.value -= ns.hacknet.getCoreUpgradeCost(index,n);
ns.hacknet.upgradeCore(index,n)
break;
case "level":
account.value -= ns.hacknet.getLevelUpgradeCost(index,n);
ns.hacknet.upgradeLevel(index,n)
break;
case "ram":
account.value -= ns.hacknet.getRamUpgradeCost(index,n);
ns.hacknet.upgradeRam(index,n)
break;
}
}
}
function getMinCENode(ns:NS, index: number, n: number):[number,ComponentKind]{
const {costEffective: ce} = getCostEffective(ns,index,n);
if(ce.core < ce.level){
if(ce.core < ce.ram){
return [ce.core, "core"];
}
else{
return [ce.ram, "ram"];
}
}
else {
if(ce.level < ce.ram){
return [ce.level,"level"];
}
else {
return [ce.ram,"ram"];
}
}
}
function* getIndexOfHackNode(ns:NS): Generator<number>{
for (const it of range(ns.hacknet.numNodes())) {
yield it;
}
}
function getMinCEIndex(ns:NS, n: number):[number,number,ComponentKind]{
let optimalC = Infinity;
let kind = "";
let minIndex = 0;
for(const it of getIndexOfHackNode(ns)){
const [c, k] = getMinCENode(ns,it,n);
if(c < optimalC){
optimalC = c;
kind = k;
minIndex = it;
}
}
return [minIndex,optimalC,kind];
}
function getBuyNodeCE(ns:NS):{cost:number;production:number;costEffective: number}{
const mult = ns.getHacknetMultipliers().production;
const bitMult = 1//ns.getBitNodeMultipliers().HacknetNodeMoney;
const cost = ns.hacknet.maxNumNodes() > ns.hacknet.numNodes() ? ns.hacknet.getPurchaseNodeCost() : Infinity;
const production = getNodeProduction(1,1,1,mult,bitMult);
return {
cost,
production,
costEffective: cost/production,
};
}
function cycle(ns:NS, account:Account): void{
const chunkN = 1;
const [upgradeNodeIndex,upgradeNodeCE,kind] = getMinCEIndex(ns,chunkN);
const {costEffective: buyNew} = getBuyNodeCE(ns);
if(upgradeNodeCE < buyNew){
const action = createUpgradeAction(ns,upgradeNodeIndex,chunkN,kind);
action(account);
}
else {
ns.hacknet.purchaseNode()
}
}
// eslint-disable-next-line require-await
export async function main(ns:NS):Promise<void>{
ns.disableLog("ALL")
const flag = parse(ns.args);
const iter = parseInt(flag.i ?? flag.iter ?? "10");
const rate = parseFloat(flag.r ?? flag.rate ?? "0.5");
const budget = parseInt(flag.b ?? flag.budget ?? "0");
const account = new Account(budget);
account.startIncrement(ns,rate);
ns.tail();
for (let i = 0; i < iter; i++) {
ns.clearLog()
ns.print(`budget ${account.value}`);
cycle(ns,account);
await ns.sleep(1000);
}
}

455
src/lib/flag.ts Normal file
View File

@ -0,0 +1,455 @@
// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.
/**
* CLI flag parser.
*
* This module is browser compatible.
*
* @module
*/
import { assert } from "../util/assert";
/** The value returned from `parse`. */
export interface Args {
/** Contains all the arguments that didn't have an option associated with
* them. */
_: Array<string | number>;
// deno-lint-ignore no-explicit-any
[key: string]: any;
}
/** The options for the `parse` call. */
export interface ParseOptions {
/** When `true`, populate the result `_` with everything before the `--` and
* the result `['--']` with everything after the `--`. Here's an example:
*
* ```ts
* // $ deno run example.ts -- a arg1
* import { parse } from "./mod.ts";
* console.dir(parse(Deno.args, { "--": false }));
* // output: { _: [ "a", "arg1" ] }
* console.dir(parse(Deno.args, { "--": true }));
* // output: { _: [], --: [ "a", "arg1" ] }
* ```
*
* Defaults to `false`.
*/
"--"?: boolean;
/** An object mapping string names to strings or arrays of string argument
* names to use as aliases. */
alias?: Record<string, string | string[]>;
/** A boolean, string or array of strings to always treat as booleans. If
* `true` will treat all double hyphenated arguments without equal signs as
* `boolean` (e.g. affects `--foo`, not `-f` or `--foo=bar`) */
boolean?: boolean | string | string[];
/** An object mapping string argument names to default values. */
default?: Record<string, unknown>;
/** When `true`, populate the result `_` with everything after the first
* non-option. */
stopEarly?: boolean;
/** A string or array of strings argument names to always treat as strings. */
string?: string | string[];
/** A string or array of strings argument names to always treat as arrays.
* Collectable options can be used multiple times. All values will be
* colelcted into one array. If a non-collectable option is used multiple
* times, the last value is used. */
collect?: string | string[];
/** A string or array of strings argument names which can be negated
* by prefixing them with `--no-`, like `--no-config`. */
negatable?: string | string[];
/** A function which is invoked with a command line parameter not defined in
* the `options` configuration object. If the function returns `false`, the
* unknown option is not added to `parsedArgs`. */
unknown?: (arg: string, key?: string, value?: unknown) => unknown;
}
interface Flags {
bools: Record<string, boolean>;
strings: Record<string, boolean>;
collect: Record<string, boolean>;
negatable: Record<string, boolean>;
unknownFn: (arg: string, key?: string, value?: unknown) => unknown;
allBools: boolean;
}
interface NestedMapping {
[key: string]: NestedMapping | unknown;
}
const { hasOwn } = Object;
function get<T>(obj: Record<string, T>, key: string): T | undefined {
if (hasOwn(obj, key)) {
return obj[key];
}
}
function getForce<T>(obj: Record<string, T>, key: string): T {
const v = get(obj, key);
assert(v != null);
return v;
}
function isNumber(x: unknown): boolean {
if (typeof x === "number") return true;
if (/^0x[0-9a-f]+$/i.test(String(x))) return true;
return /^[-+]?(?:\d+(?:\.\d*)?|\.\d+)(e[-+]?\d+)?$/.test(String(x));
}
function hasKey(obj: NestedMapping, keys: string[]): boolean {
let o = obj;
keys.slice(0, -1).forEach((key) => {
o = (get(o, key) ?? {}) as NestedMapping;
});
const key = keys[keys.length - 1];
return key in o;
}
/** Take a set of command line arguments, optionally with a set of options, and
* return an object representing the flags found in the passed arguments.
*
* By default any arguments starting with `-` or `--` are considered boolean
* flags. If the argument name is followed by an equal sign (`=`) it is
* considered a key-value pair. Any arguments which could not be parsed are
* available in the `_` property of the returned object.
*
* ```ts
* import { parse } from "./mod.ts";
* const parsedArgs = parse(Deno.args);
* ```
*
* ```ts
* import { parse } from "./mod.ts";
* const parsedArgs = parse(["--foo", "--bar=baz", "--no-qux", "./quux.txt"]);
* // parsedArgs: { foo: true, bar: "baz", qux: false, _: ["./quux.txt"] }
* ```
*/
export function parse(
args: string[],
{
"--": doubleDash = false,
alias = {},
boolean = false,
default: defaults = {},
stopEarly = false,
string = [],
collect = [],
negatable = [],
unknown = (i: string): unknown => i,
}: ParseOptions = {},
): Args {
const flags: Flags = {
bools: {},
strings: {},
unknownFn: unknown,
allBools: false,
collect: {},
negatable: {},
};
if (boolean !== undefined) {
if (typeof boolean === "boolean") {
flags.allBools = !!boolean;
} else {
const booleanArgs = typeof boolean === "string" ? [boolean] : boolean;
for (const key of booleanArgs.filter(Boolean)) {
flags.bools[key] = true;
}
}
}
const aliases: Record<string, string[]> = {};
if (alias !== undefined) {
for (const key in alias) {
const val = getForce(alias, key);
if (typeof val === "string") {
aliases[key] = [val];
} else {
aliases[key] = val;
}
for (const alias of getForce(aliases, key)) {
aliases[alias] = [key].concat(aliases[key].filter((y) => alias !== y));
}
}
}
if (string !== undefined) {
const stringArgs = typeof string === "string" ? [string] : string;
for (const key of stringArgs.filter(Boolean)) {
flags.strings[key] = true;
const alias = get(aliases, key);
if (alias) {
for (const al of alias) {
flags.strings[al] = true;
}
}
}
}
if (collect !== undefined) {
const collectArgs = typeof collect === "string" ? [collect] : collect;
for (const key of collectArgs.filter(Boolean)) {
flags.collect[key] = true;
const alias = get(aliases, key);
if (alias) {
for (const al of alias) {
flags.collect[al] = true;
}
}
}
}
if (negatable !== undefined) {
const negatableArgs = typeof negatable === "string"
? [negatable]
: negatable;
for (const key of negatableArgs.filter(Boolean)) {
flags.negatable[key] = true;
const alias = get(aliases, key);
if (alias) {
for (const al of alias) {
flags.negatable[al] = true;
}
}
}
}
const argv: Args = { _: [] };
function argDefined(key: string, arg: string): boolean {
return (
(flags.allBools && /^--[^=]+$/.test(arg)) ||
get(flags.bools, key) ||
!!get(flags.strings, key) ||
!!get(aliases, key)
);
}
function setKey(
obj: NestedMapping,
name: string,
value: unknown,
collect = true,
): void {
let o = obj;
const keys = name.split(".");
keys.slice(0, -1).forEach(function (key): void {
if (get(o, key) === undefined) {
o[key] = {};
}
o = get(o, key) as NestedMapping;
});
const key = keys[keys.length - 1];
const collectable = collect && !!get(flags.collect, name);
if (!collectable) {
o[key] = value;
} else if (get(o, key) === undefined) {
o[key] = [value];
} else if (Array.isArray(get(o, key))) {
(o[key] as unknown[]).push(value);
} else {
o[key] = [get(o, key), value];
}
}
function setArg(
key: string,
val: unknown,
arg: string | undefined = undefined,
collect?: boolean,
): void {
if (arg && flags.unknownFn && !argDefined(key, arg)) {
if (flags.unknownFn(arg, key, val) === false) return;
}
const value = !get(flags.strings, key) && isNumber(val) ? Number(val) : val;
setKey(argv, key, value, collect);
const alias = get(aliases, key);
if (alias) {
for (const x of alias) {
setKey(argv, x, value, collect);
}
}
}
function aliasIsBoolean(key: string): boolean {
return getForce(aliases, key).some(
(x) => typeof get(flags.bools, x) === "boolean",
);
}
let notFlags: string[] = [];
// all args after "--" are not parsed
if (args.includes("--")) {
notFlags = args.slice(args.indexOf("--") + 1);
args = args.slice(0, args.indexOf("--"));
}
for (let i = 0; i < args.length; i++) {
const arg = args[i];
if (/^--.+=/.test(arg)) {
const m = arg.match(/^--([^=]+)=(.*)$/s);
assert(m != null);
const [, key, value] = m;
if (flags.bools[key]) {
const booleanValue = value !== "false";
setArg(key, booleanValue, arg);
} else {
setArg(key, value, arg);
}
} else if (
/^--no-.+/.test(arg) && get(flags.negatable, arg.replace(/^--no-/, ""))
) {
const m = arg.match(/^--no-(.+)/);
assert(m != null);
setArg(m[1], false, arg, false);
} else if (/^--.+/.test(arg)) {
const m = arg.match(/^--(.+)/);
assert(m != null);
const [, key] = m;
const next = args[i + 1];
if (
next !== undefined &&
!/^-/.test(next) &&
!get(flags.bools, key) &&
!flags.allBools &&
(get(aliases, key) ? !aliasIsBoolean(key) : true)
) {
setArg(key, next, arg);
i++;
} else if (/^(true|false)$/.test(next)) {
setArg(key, next === "true", arg);
i++;
} else {
setArg(key, get(flags.strings, key) ? "" : true, arg);
}
} else if (/^-[^-]+/.test(arg)) {
const letters = arg.slice(1, -1).split("");
let broken = false;
for (let j = 0; j < letters.length; j++) {
const next = arg.slice(j + 2);
if (next === "-") {
setArg(letters[j], next, arg);
continue;
}
if (/[A-Za-z]/.test(letters[j]) && /=/.test(next)) {
setArg(letters[j], next.split(/=(.+)/)[1], arg);
broken = true;
break;
}
if (
/[A-Za-z]/.test(letters[j]) &&
/-?\d+(\.\d*)?(e-?\d+)?$/.test(next)
) {
setArg(letters[j], next, arg);
broken = true;
break;
}
if (letters[j + 1] && letters[j + 1].match(/\W/)) {
setArg(letters[j], arg.slice(j + 2), arg);
broken = true;
break;
} else {
setArg(letters[j], get(flags.strings, letters[j]) ? "" : true, arg);
}
}
const [key] = arg.slice(-1);
if (!broken && key !== "-") {
if (
args[i + 1] &&
!/^(-|--)[^-]/.test(args[i + 1]) &&
!get(flags.bools, key) &&
(get(aliases, key) ? !aliasIsBoolean(key) : true)
) {
setArg(key, args[i + 1], arg);
i++;
} else if (args[i + 1] && /^(true|false)$/.test(args[i + 1])) {
setArg(key, args[i + 1] === "true", arg);
i++;
} else {
setArg(key, get(flags.strings, key) ? "" : true, arg);
}
}
} else {
if (!flags.unknownFn || flags.unknownFn(arg) !== false) {
argv._.push(flags.strings["_"] ?? !isNumber(arg) ? arg : Number(arg));
}
if (stopEarly) {
argv._.push(...args.slice(i + 1));
break;
}
}
}
for (const [key, value] of Object.entries(defaults)) {
if (!hasKey(argv, key.split("."))) {
setKey(argv, key, value);
if (aliases[key]) {
for (const x of aliases[key]) {
setKey(argv, x, value);
}
}
}
}
for (const key of Object.keys(flags.bools)) {
if (!hasKey(argv, key.split("."))) {
const value = get(flags.collect, key) ? [] : false;
setKey(
argv,
key,
value,
false,
);
}
}
for (const key of Object.keys(flags.strings)) {
if (!hasKey(argv, key.split(".")) && get(flags.collect, key)) {
setKey(
argv,
key,
[],
false,
);
}
}
if (doubleDash) {
argv["--"] = [];
for (const key of notFlags) {
argv["--"].push(key);
}
} else {
for (const key of notFlags) {
argv._.push(key);
}
}
return argv;
}

95
src/lib/hack.ts Normal file
View File

@ -0,0 +1,95 @@
import { NS, Server } from '@ns'
import {selectAllServerList, ServerInfo} from "./servers"
export type Hackability = "hackable" | "rooted" | "impossible"
export function getHackability(ns: NS,hostname:string): Hackability{
if (ns.hasRootAccess(hostname)) {
return "rooted";
}
if (ns.getServerRequiredHackingLevel(hostname) > ns.getHackingLevel() ||
ns.getServerNumPortsRequired(hostname) > hackablePorts(ns)) {
return "impossible";
}
return "hackable";
}
export function getHackabilityServer(ns: NS,server:Server): Hackability{
if (server.hasAdminRights) {
return "rooted";
}
if (server.requiredHackingSkill > ns.getHackingLevel() ||
server.numOpenPortsRequired > hackablePorts(ns)) {
return "impossible";
}
return "hackable";
}
export function getLockSymbol(k:Hackability):string{
switch (k) {
case "rooted":
return "🔓"
case "hackable":
return "🔐"
case "impossible":
return "🔒"
}
}
type Hack = {
file: string;
exec: (ns: NS, target:string)=> void;
};
export const hacks: Hack[] = [
{ file: 'BruteSSH.exe', exec: (ns: NS, target: string) => ns.brutessh(target) },
{ file: 'FTPCrack.exe', exec: (ns: NS, target: string) => ns.ftpcrack(target) },
{ file: 'relaySMTP.exe', exec: (ns: NS, target: string) => ns.relaysmtp(target) },
{ file: 'HTTPWorm.exe', exec: (ns: NS, target: string) => ns.httpworm(target) },
{ file: 'SQLInject.exe', exec: (ns: NS, target: string) => ns.sqlinject(target) },
]
/**
* @param {NS} ns
*/
export function hackablePorts(ns: NS): number {
return hacks.map(x=>(ns.fileExists(x.file) ? 1 : 0)).reduce((x,y)=>x+y)
}
export function isHackableServer(ns: NS, target: Server): boolean{
return target.numOpenPortsRequired <= hackablePorts(ns) &&
target.hackDifficulty <= ns.getHackingLevel()
}
export function selectHackableServerList(ns: NS): ServerInfo[]{
return selectAllServerList(ns).filter(x=>{
return isHackableServer(ns,x)
})
}
export function hackServer(ns:NS,target:Server);
export function hackServer(ns:NS,target:string);
export function hackServer(ns:NS,target:string|Server):void{
const hostname = typeof target === "string" ? target : target.hostname
hacks.forEach(x=>{
if(ns.fileExists(x.file,"home")){
x.exec(ns, hostname)
}
})
ns.nuke(hostname)
}
/** @param {NS} ns */
// eslint-disable-next-line require-await
export async function main(ns: NS):Promise<void> {
if(ns.args.length == 0){
ns.tprint("argument required")
ns.tprint("run cmd [target]")
return
}
const target = ns.args[0]
hackServer(target)
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export function autocomplete(data : ServerData, args : string[]) : string[] {
return [...data.servers]
}

71
src/lib/servers.ts Normal file
View File

@ -0,0 +1,71 @@
import { NS, Server } from '@ns'
/** @param {NS} ns */
export async function main(ns: NS): Promise<void> {
if (ns.args.length == 0) {
ns.tprint("argument required")
ns.tprint("search all server and print to json")
ns.tprint("run cmd [filePath]")
ns.exit()
}
const filePath = ns.args[0]
const collectedData = selectAllServerList()
const buf = JSON.stringify(collectedData, undefined, 2)
await ns.write(filePath, buf, "w")
}
export interface ServerInfo extends Server{
relHost:string[];
}
/**
* @param {NS} ns
* @param func
*/
export function visitAllServerList(ns: NS, func:(data: ServerInfo)=>void):void {
/** @type {string[]} */
const queue = ["home"]
/** @type {Set<string>} */
const visited = new Set()
//breadth first search
while (queue.length > 0) {
const vHost = queue.pop()
const data = getServersInfo(ns, vHost)
func(data)
visited.add(vHost)
for (const h of data.relHost) {
if (!visited.has(h)) {
queue.push(h)
}
}
}
}
/**@param {NS} ns */
export function selectAllServerList(ns: NS): ServerInfo[] {
const collectedData: ServerInfo[] = []
visitAllServerList(ns, (data) => {
collectedData.push(data)
})
return collectedData
}
export function isRootedServer(server:Server):boolean{
return server.hasAdminRights
}
export function selectRootedServerList(ns:NS): ServerInfo[]{
return selectAllServerList(ns).filter(isRootedServer)
}
/** @param {NS} ns
* @param {string} host
* @return {Server & {relHost: string[]}}
*/
export function getServersInfo(ns: NS, host: string): ServerInfo {
const neighbor = ns.scan(host)
const info = ns.getServer(host)
return { ...info, relHost: neighbor }
}

27
src/list-all-server.ts Normal file
View File

@ -0,0 +1,27 @@
import {NS} from "@ns"
import {getHackability,getLockSymbol} from "./lib/hack";
/** @param {NS} ns */
// eslint-disable-next-line require-await
export async function main(ns: NS): Promise<void> {
findServer(ns, 'home', 'home', 1);
}
/** @param {NS} ns
* @param {string} startServer
* @param {string} targetServer
* @param {number} depth
*/
function findServer(ns: NS, startServer: string, targetServer: string, depth: number): void {
const servers = ns.scan(targetServer)
.filter((server) => server !== startServer);
servers.forEach((server) => {
const lock = getHackability(ns, server);
const lock_symbol = getLockSymbol(lock);
const info = ns.getServer(server);
ns.tprint(`😹${'>'.repeat(depth)} ${lock_symbol} ${server} ${info.backdoorInstalled ? '✅': '❌'}`);
if (lock !== "impossible") {
findServer(ns, targetServer, server, depth + 1);
}
});
}

View File

@ -0,0 +1,13 @@
import {NS} from "@ns"
import {parse} from "lib/flag"
/** @param {NS} ns */
// eslint-disable-next-line require-await
export async function main(ns: NS):Promise<void> {
const flag = parse(ns.args);
if(flag.help || flag.h){
ns.tprint(`* list all purchased server`);
ns.exit();
}
ns.tprint(ns.getPurchasedServers().join("\n"));
}

72
src/purchase-server.ts Normal file
View File

@ -0,0 +1,72 @@
import { NS } from "@ns"
import {parse} from "./lib/flag"
import {range} from "./util/range";
/** @param {NS} ns */
export async function main(ns: NS): Promise<void> {
const flag = parse(ns.args);
if(flag.h || flag.help){
ns.tprint("script : purchase server")
ns.tprint("");
ns.exit()
}
if(Boolean(flag.i) || Boolean(flag.interactive)){
const ramLimit = ns.getPurchasedServerMaxRam()
const choices = [...range(3,20)].map(x=>Math.pow(2,x)).filter(x=>x <= ramLimit).map((x)=>{
const cost = ns.getPurchasedServerCost(x);
return `${x}GB (${ns.nFormat(cost,"$0.000a")})`
});
const choice = await ns.prompt("which server do you purchase?",{
type:"select",
choices
});
if(choice === ""){
ns.tprint("canceled");
ns.exit();
return;
}
const gb = parseInt(/^(\d+)GB/.exec(choice)[1]);
ns.tprint("you select ",gb,"GB");
const hostname = await ns.prompt("name your server",{type:"text"});
if(hostname === ""){
ns.tprint("canceled");
ns.exit();
return;
}
const p = await ns.prompt(
`do you want to purchase server ${gb}GB(${
ns.nFormat(ns.getPurchasedServerCost(gb),"$0.000a")})?`,
{ type: "boolean" });
if (p) {
const l = ns.purchaseServer(hostname, ram)
ns.tprint(l, " purchased");
}
ns.exit()
return;
}
if ((flag.n || flag.name) && (flag.s || flag.size)) {
ns.tprint("argument needed")
ns.tprint("run cmd -n [hostname] -s [ram(GB)]")
ns.tprint("run cmd --name [hostname] --size [ram(GB)]")
ns.exit()
return;
}
const hostname = flag.n ?? flag.name
const ram = parseInt(flag.s ?? flag.size)
if(isNaN(ram)){
ns.tprint("size must be integer!");
ns.exit();
return;
}
const f = ns.getPurchasedServerCost(ram)
const ff = ns.nFormat(f, "($ 0.00 a)")
const p = await ns.prompt("required : " + ff + "\ndo you want to purchase server " + ram + "GB?",
{ type: "boolean" })
if (p) {
const l = ns.purchaseServer(hostname, ram)
ns.tprint(l, " purchased")
}
}

79
src/server-status.ts Normal file
View File

@ -0,0 +1,79 @@
import {NS} from "@ns";
import {hackablePorts, getHackability, getLockSymbol} from "./lib/hack";
import {parse} from "./lib/flag";
type ReportOption = {
detail?: boolean;
}
/*
* Utility functions that report serverStatus
* and Hackability
* @param {NS} ns
*/
// eslint-disable-next-line require-await
export async function main(ns: NS): Promise<void> {
const flag = parse(ns.args);
if (flag._.length == 0) {
ns.tprint("argumented required");
return;
}
const hostname = flag._[0];
const detail = Boolean(flag.d) || Boolean(flag.detail);
serverReport(ns, hostname,{
detail: detail,
});
}
/**
* @param {NS} ns
* @param {string} hostname
*/
export function serverReport(ns: NS, hostname: string, options?:ReportOption):void {
options ??= {};
const serverLock = getHackability(ns, hostname);
ns.tprint(`${getLockSymbol(serverLock)} ${hostname}`);
if(options.detail){
const server = ns.getServer(hostname);
const msg = ['',
`hostname : ${server.hostname}(${server.ip})`,
`🛡️${ns.nFormat(server.hackDifficulty,"0[.]000")}/${server.minDifficulty}(${server.baseDifficulty})`,
`💸${ns.nFormat(server.moneyAvailable,"$0.000a")}/${ns.nFormat(server.moneyMax,"$0.000a")}`,
`💾${server.ramUsed}GB/${server.maxRam}GB`,
`backdoorInstalled \t: ${server.backdoorInstalled ? `🚪` : ``}`,
`cpu \t\t\t: ${server.cpuCores}`,
`growth \t\t\t: ${server.serverGrowth}`,
`organization name\t: ${server.organizationName}`,
`purchased \t\t: ${server.purchasedByPlayer}`,
`required hacking skill\t: ${server.requiredHackingSkill}`,
`ports \t\t\t: ${server.openPortCount}/${server.numOpenPortsRequired}`
].join("\n");
ns.tprint(msg);
}
else{
if (serverLock == "rooted") {
ns.tprint(`🛡️${Math.round(ns.getServerSecurityLevel(hostname))}/${ns.getServerMinSecurityLevel(hostname)}`);
ns.tprint(`💸${ns.nFormat(ns.getServerMoneyAvailable(hostname), "$0.000a")}/${ns.nFormat(ns.getServerMaxMoney(hostname), "$0.000a")}`);
} else {
ns.tprint(`Hack Level: ${ns.getServerRequiredHackingLevel(hostname)}`);
ns.tprint(`Ports: ${ns.getServerNumPortsRequired(hostname)}`);
}
}
}
/**
* @param {NS} ns
*/
export function serverHackStatus(ns: NS, server: string): string {
if (ns.hasRootAccess(server)) {
return "🔓";
}
if (ns.getServerRequiredHackingLevel(server) > ns.getHackingLevel() ||
ns.getServerNumPortsRequired(server) > hackablePorts(ns)) {
return "🔒";
}
return "🔐";
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export function autocomplete(data : ServerData, args : string[]) : string[] {
return [...data.servers]
}

8
src/share-server.ts Normal file
View File

@ -0,0 +1,8 @@
import {NS} from "@ns"
// eslint-disable-next-line require-await
export async function main(ns:NS):Promise<void>{
while(true){
await ns.share()
}
}

14
src/util/assert.ts Normal file
View File

@ -0,0 +1,14 @@
import assert from "assert";
export class AssertionError extends Error{
constructor(msg:string){
super(msg);
this.name = "AssertionError";
}
}
export function assert(expr: unknown, msg=""): asserts expr{
if(!expr){
throw new AssertionError(msg)
}
}

9
src/util/range.ts Normal file
View File

@ -0,0 +1,9 @@
export function* range(end: number): Generator<number>;
export function* range(begin:number,end:number): Generator<number>;
export function* range(arg1: number, arg2?: number): Generator<number>{
const begin = arg2 ? arg1 : 0;
const end = arg2 ?? arg1;
for(let i = begin; i<end; i++){
yield i;
}
}

View File

@ -6,15 +6,15 @@ export async function main(ns: NS): Promise<void> {
const files = ns.ls('home', '.js')
for (const file of files) {
const contents = ns.read(file)
hashes[file] = getHash(contents)
hashes[file] = getHash(contents as string)
}
// eslint-disable-next-line no-constant-condition
while (true) {
const files = ns.ls('home', '.js')
for (const file of files) {
const contents = ns.read(file)
const hash = getHash(contents)
const hash = getHash(contents as string)
if (hash != hashes[file]) {
ns.tprintf(`INFO: Detected change in ${file}`)

View File

@ -11,7 +11,6 @@
"noImplicitThis": true,
"esModuleInterop": true,
"inlineSourceMap": true,
"sourceRoot": "http://localhost:8000/sources/",
"strict": true,
"rootDir": "src/",
"outDir": "dist/",